Как да поддържате агрегати в 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-way) да направите това? Как може да изглежда?

Въпросът е за дизайн на интерфейс и за възможност за (вътрешно) внедряване (не подробности).

Сигурен съм, че контейнерите използват нещо като ::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'}); работи в C++11.   -  person Chad    schedule 29.12.2016
comment
@Chad да, AFAIK, но 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 просто ще използва поставяне new със синтаксис за инициализация ().

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

В C++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