Следующий код не скомпилируется на gcc 4.8.2.
Проблема в том, что этот код будет пытаться скопировать std::pair<int, A>
что не может произойти из-за struct A
отсутствует копирование и перемещение конструкторов.
GCC здесь не работает или я что-то упустил?
#include <map>
struct A
{
int bla;
A(int blub):bla(blub){}
A(A&&) = delete;
A(const A&) = delete;
A& operator=(A&&) = delete;
A& operator=(const A&) = delete;
};
int main()
{
std::map<int, A> map;
map.emplace(1, 2); // doesn't work
map.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(2)
); // works like a charm
return 0;
}
Насколько я могу судить, проблема не вызвана 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, которые, насколько я могу судить, оба используют libstdc ++.
Проблема коренится в том, что, хотя мы можем построить
A t(4);
то есть, std::is_constructible<A, int>::value == true
, мы не можем неявно конвертировать int
для A
[Ко] / 3
Выражение
e
может быть неявно преобразуется к типуT
если и только если декларацияT t=e;
хорошо сформирован,
для какой-то придуманной временной переменнойt
,
Обратите внимание на копию инициализации ( =
). Это создает временный A
и инициализирует t
из этого временного, [dcl.init] / 17. Эта инициализация из временного пытается вызвать удаленный ctor перемещения A
, что делает преобразование плохо сформированным.
Поскольку мы не можем преобразовать из int
для A
конструктор pair
что можно было бы ожидать, чтобы быть названным отклонено SFINAE. Такое поведение удивительно, N4387 — Улучшение пары и кортежа анализирует и пытается улучшить ситуацию, делая конструктор explicit
вместо того, чтобы отвергать это. N4387 был избран в C ++ 1z на собрании в Ленексе.
Ниже описаны правила C ++ 11.
Конструктор, который я ожидал вызвать, описан в [pair.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
этот
конструктор не должен участвовать в разрешении перегрузки.
Обратите внимание на разницу между is_constructible
в требует раздел, и «не является неявно конвертируемым» в замечания раздел. Требования выполняются для вызова этого конструктора, но он может не участвовать в разрешении перегрузки (= должен быть отклонен через SFINAE).
Следовательно, для разрешения перегрузки необходимо выбрать «худшее соответствие», а именно то, чей второй параметр является A const&
, Временный создается из int
аргумент и привязан к этой ссылке, и ссылка используется для инициализации pair
элемент данных (.second
). Инициализация пытается вызвать удаленную копию ctor A
и конструкция пары плохо сформирована.
libstdc ++ имеет (как расширение) несколько нестандартных ctors. в последний доксиген (и в 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>()...);
T
и любой изArgs
, Рассматривается только действительность непосредственного контекста инициализации переменной.
is_convertible
[Meta.unary.prop] / 6:
Дан следующий прототип функции:
template <class T> typename add_rvalue_reference<T>::type create();
условие предиката для специализации шаблона
is_convertible<From, To>
должны быть удовлетворены, если и
только если выражение возврата в следующем коде будет правильно сформировано, включая любые неявные преобразования
к типу возвращаемого значения функции:[Замечания: Это требование дает четко определенные результаты для ссылочных типов, типов void, типов массивов и типов функций. — конечная нота] Проверка доступа выполняется как в контексте, не связанном сTo test() { return create<From>(); }
To
а такжеFrom
, Только
действительность непосредственного контекста выражения возвратного заявление (включая преобразования в
тип возврата) считается.
Для вашего типа A
,
A t(create<int>());
хорошо сформирован; тем не мение
A test() {
return create<int>();
}
создает временный тип A
и пытается переехать это в возвращаемое значение (копия-инициализация). Это выбирает удаленный ctor A(A&&)
и поэтому плохо сформирован.
Других решений пока нет …