Ответ 1
Насколько я могу судить, проблема не вызвана map::emplace
, а конструкторами pair
:
#include <map>
struct A
{
A(int) {}
A(A&&) = delete;
A(A const&) = delete;
};
int main()
{
std::pair<int, A> x(1, 4); // error
}
Этот пример кода не компилируется ни с помощью coliru g++ 4.8.1, ни с clang++ 3.5, которые оба используют libstdС++, насколько я могу судить.
Проблема связана с тем, что, хотя мы можем построить
A t(4);
то есть std::is_constructible<A, int>::value == true
, мы не можем неявно преобразовать int
в A
[conv]/3
Выражение
e
может быть неявно преобразовано в типT
тогда и только тогда, когда декларацияT t=e;
хорошо сформирована, для некоторой изобретенной временной переменнойT
.
Обратите внимание на инициализацию копирования (=
). Это создает временную A
и инициализирует T
из этого временного файла [dcl.init]/17. Эта инициализация из временного пытается вызвать удаленное перемещение ctor A
, что делает преобразование плохо сформированным.
Поскольку мы не можем преобразовать из int
в A
, конструктор pair
, который можно было бы ожидать, отклоняется SFINAE. Такое поведение удивительно, N4387 - Улучшение пары и кортежа анализирует и пытается улучшить ситуацию, сделав вместо этого конструктор explicit
, N4387 был проголосован на С++ 1z на собрании Lenexa.
Ниже описываются правила С++ 11.
Конструктор, который я ожидал вызвать, описан в [pairs.pair]/7-9
template<class U, class V> constexpr pair(U&& x, V&& y);
7 Требуется:
is_constructible<first_type, U&&>::value
-true
иis_constructible<second_type, V&&>::value
true
.8 Эффекты: конструктор сначала инициализирует
std::forward<U>(x)
, а второй -std::forward<V>(y)
.9 Замечания: если
U
неявно конвертируется вfirst_type
илиV
неявно конвертируется вsecond_type
this конструктор не участвует в разрешении перегрузки.
Обратите внимание на разницу между is_constructible
в разделе Requires и "неявно конвертируется" в разделе "Примечания". Требования выполняются для вызова этого конструктора, но он может не участвовать в разрешении перегрузки (= должен быть отклонен через SFINAE).
Следовательно, для разрешения перегрузки необходимо выбрать "худшее совпадение", а именно: второй параметр - A const&
. Временное создается из аргумента int
и привязывается к этой ссылке, а эта ссылка используется для инициализации элемента данных pair
(.second
). Инициализация пытается вызвать удаленную копию ctor A
, а конструкция пары плохо сформирована.
libstdС++ имеет (как расширение) некоторые нестандартные ctors. В последнем doxygen (и в 4.8.2) конструктор pair
, который я ожидал назвать (будучи удивлен правила, требуемые Стандартом):
template<class _U1, class _U2,
class = typename enable_if<__and_<is_convertible<_U1, _T1>,
is_convertible<_U2, _T2>
>::value
>::type>
constexpr pair(_U1&& __x, _U2&& __y)
: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }
и тот, который на самом деле называется, является нестандартным:
// DR 811.
template<class _U1,
class = typename enable_if<is_convertible<_U1, _T1>::value>::type>
constexpr pair(_U1&& __x, const _T2& __y)
: first(std::forward<_U1>(__x)), second(__y) { }
Программа плохо сформирована в соответствии со стандартом, она не просто отвергается этим нестандартным ctor.
Как заключительное замечание, здесь спецификация is_constructible
и is_convertible
.
is_constructible
[meta.rel]/4
Учитывая следующий прототип функции:
template <class T> typename add_rvalue_reference<T>::type create();
условие предиката для специализированной специализации
is_constructible<T, Args...>
должно выполняться тогда и только тогда, когда следующее определение переменной будет хорошо сформировано для некоторой изобретенной переменнойT
:T t(create<Args>()...);
[Примечание. Эти токены никогда не интерпретируются как объявление функции. - end note] Проверка доступа выполняется как в контексте, не связанным с
T
и любым изArgs
. Учитывается только действительность непосредственного контекста инициализации переменных.
is_convertible
[meta.unary.prop]/6:
Учитывая следующий прототип функции:
template <class T> typename add_rvalue_reference<T>::type create();
условие предиката для типовой специализации
is_convertible<From, To>
должно выполняться, если и только если выражение возврата в следующем коде будет хорошо сформировано, включая любые неявные преобразования к возвращаемому типу функции:To test() { return create<From>(); }
[Примечание. Это требование дает хорошо определенные результаты для ссылочных типов, типов пустот, типов массивов и типов функций. - end note] Проверка доступа выполняется так, как если бы в контексте, не связанном с
To
иFrom
. Только справедливость непосредственного контекста выражения return-statement (включая преобразования в тип возврата).
Для вашего типа A
,
A t(create<int>());
хорошо сформирован; Однако
A test() {
return create<int>();
}
создает временный тип A
и пытается переместить его в возвращаемое значение (копирование-инициализация). Это выбирает удаленный ctor A(A&&)
и поэтому плохо сформирован.