почему мне нужно использовать piecewise_construct в map :: emplace для конструкторов с одним аргументом некопируемых объектов?

Следующий код не скомпилируется на 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;
}

21

Решение

Насколько я могу судить, проблема не вызвана 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> должны быть удовлетворены, если и
только если выражение возврата в следующем коде будет правильно сформировано, включая любые неявные преобразования
к типу возвращаемого значения функции:

To test() {
return create<From>();
}
[Замечания: Это требование дает четко определенные результаты для ссылочных типов, типов void, типов массивов и типов функций. — конечная нота] Проверка доступа выполняется как в контексте, не связанном с To а также From, Только
действительность непосредственного контекста выражения возвратного заявление (включая преобразования в
тип возврата) считается.


Для вашего типа A,

A t(create<int>());

хорошо сформирован; тем не мение

A test() {
return create<int>();
}

создает временный тип A и пытается переехать это в возвращаемое значение (копия-инициализация). Это выбирает удаленный ctor A(A&&) и поэтому плохо сформирован.

19

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]