Как поддерживать агрегаты в STL или STL-подобной библиотеке

Как ввести поддержку агрегатной инициализации для их правильного построения в (например) контейнерах STL? Я имею в виду что-то вроде:

struct A { int i; char c; };
std::list< A > l; // empty
l.insert(std::memberwise, 1, '2');
// <=> l.insert({1, '2'});

std::memberwise — возможный тег, подобный уже существующим в STL std::piecewise_construct, std::allocator_arg и т. д.

Теоретически возможно ли расширять контейнеры STL таким образом? Есть ли лучший способ (STL-способ) сделать это? Как это может выглядеть?

Вопрос о дизайне интерфейса и о возможности (внутренней) реализации (не детали).

Я уверен, что контейнеры используют что-то вроде ::new (static_cast< void * >(pstorage) value_type(std::forward< Args >(args)...); внутри. Я уверен, что замена скобок фигурными скобками приведет к серьезным изменениям. Из-за отсутствия сужения, т.е.

КРОМЕ ТОГО

Как правило, l.insert({1, '2'});, упомянутые в комментариях, могут привести к чрезмерному перемещению value_type. Весьма вероятно, что этот шаг был бы оптимизирован любым современным компилятором, но в любом случае есть лишние фигурные скобки.


person Tomilov Anatoliy    schedule 29.12.2016    source источник
comment
В вашем конкретном случае l.insert({1, '2'}); работает на С++ 11.   -  person Chad    schedule 29.12.2016
comment
@ Чад, да, насколько я знаю, но l.emplace{,_back}(1, '2'); - нет.   -  person Tomilov Anatoliy    schedule 29.12.2016
comment
@Orient Возможно, вы захотите добавить конструктор для своей структуры, который инициализирует i и c. Насколько я знаю, для функций контейнера emplace для создания объекта на месте вам это нужно. Если вы выполняете emplace или emplace_back через агрегатную инициализацию, он фактически создает объект, а затем копирует его в структуру.   -  person RyanP    schedule 29.12.2016
comment
@RyanP Для универсального кода это невозможно. Нет std::is_aggregate для обнаружения агрегатов, чтобы поместить их в агрегатную оболочку.   -  person Tomilov Anatoliy    schedule 29.12.2016


Ответы (1)


Вы должны использовать конструкцию в стиле emplace. Контейнеры стандартных библиотек передают такую ​​конструкцию в allocator_traits<Alloc>::construct<T>, которая, как ожидается, будет функцией с переменным числом аргументов. Если Alloc не имеет функции-члена construct, то allocator_traits::construct будет просто использовать новое размещение с синтаксисом инициализации ().

Очевидно, что это не позволяет выполнять агрегатную инициализацию через emplace. Однако вы можете эффективно реализовать решение, предложенное LWG 2089, предоставив свой собственный распределитель, возможно производный от std::allocator, у которого есть собственный метод construct. Ваш construct должен использовать new(p) T(...) только в том случае, если is_constructible<T, ...>::value верно. Если это не так, то вы используете new(p) T{...}.

На С++ 17 это на самом деле несложно написать:

template< class U, class... Args >
void construct( U* p, Args&&... args )
{
    if constexpr(std::is_constructible_v<U, Args...>)
        ::new((void*)p) U(std::forward<Args>(args)...);
    else
        ::new((void*)p) U{std::forward<Args>(args)...};
}
person Nicol Bolas    schedule 29.12.2016
comment
На самом деле мне не нужен бедный std::initializer_list (из-за того, что он не может двигаться), поэтому решение кажется мне идеальным. Спасибо. - person Tomilov Anatoliy; 29.12.2016
comment
Это намеренно operator new не является глобальным, а operator new применяется к указателю, отличному от void? Это точно правильное использование перегрузки operator new в Allocator::construct? - person Tomilov Anatoliy; 30.12.2016
comment
В Примечаниях здесь: the code that wants to ensure that the true placement new is called (e.g. std::allocator::construct), must use ::new and also cast the pointer to void. - person Tomilov Anatoliy; 30.12.2016
comment
Это также указывает, какой из них следует использовать для шаблона функции-члена construct. - person Tomilov Anatoliy; 30.12.2016