Добавить тэг std::aggregate_t и использующие его фабричные функции

Олег Фатхиев
Олег Фатхиев

Бывает нужно, чтобы работал такой код:

struct some_aggregate_t {
    int i;
    std::string s;
};

auto value = std::make_unique<some_aggregate_t>(5, "lol"); // error: no matching constructor for initialization of 'some_aggregate_t'

std::make_unique пытается вызвать конструктор у some_aggregate_t, вместо aggregate initialization. Поведение ожидаемое и понятное, но хочется уметь создавать такие объекты без необоходимости дописывать конструктор.

Еще один пример:

auto v = std::make_shared<std::vector<int>>(5, 2);
assert(*v == std::vector<int>{2, 2, 2, 2, 2});

В данном коде создается вектор с 5-ью 2-ками.

Предлагается добавить tag type std::aggregate_t, позволяющий специализировать вызов std::make_unique и std::make_shared:

auto valid = std::make_unique(std::aggregate, 5, "lol"); // ok

auto v = std::make_shared<std::vector<int>>(std::aggregate, 5, 2);
assert(*v == std::vector<int>{5, 2});

Возможная реализация std::make_unique:

template <class T, class... Args>
std::unique_ptr<T> make_unique(std::aggregate_t, Args&&... args) {
    return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
5
рейтинг
8 комментариев
Andrey Davydov

Кажется, что принятый в C++20 proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0960r1.html делает ненужным Ваше предлажение.

Andrey Davydov
Олег Фатхиев

Andrey Davydov, действительно, это решает одну из проблем. Вызов list initializing это не чинит.

Олег Фатхиев
Andrey Davydov

Олег Фатхиев, что Вы имеете в виду? Какой из Ваших примеров это не чинит?

Если что, std::vector считается ненормальным, классы для которых, X(2, 5) и X{2, 5} означают разное считаются ошибкой природы, и ради них создавать новый механизм (std::aggregate) это черезчур.

Andrey Davydov
Олег Фатхиев

Andrey Davydov, получается, что любой класс, имеющий конструктор от std::initializer_list, ненормальный?

Олег Фатхиев
Andrey Davydov

Олег Фатхиев, ок, т.е. речь идет именно о классах конструирующихся от std::initializer_list? Для них есть неоднозначность, которую можно решать, предложенным Вами способом (только имя std::aggregate будет не очень удачным), а можно и не решать, так ли хуже 

std::make_shared<std::vector<int>>(std::initializer_list{5, 2})

чем предлагаемый Вами 

std::make_shared<std::vector<int>>(std::aggregate, 5, 2)

?

Теперь по поводу "ненормальных" классов. Я, наверное, резко выразился, но ситуация когда для класса {} и () инициализация приводит к разным результатам действительно крайне специфичная. Она же встречается не для любого вектора и любых аргументов, а только вектора целых чисел, конструирующегося от 1-2 аргументов. И да, я думаю, что возникновение подобных ситуацией это проблема в дизайне std::vector

Andrey Davydov
Анатолий Томилов

Andrey Davydov, 

std::initializer_list -- это ведь обязательное поэлементное копирование (именно копирование и именно неизбежное).

Анатолий Томилов
Обновлено 
Andrey Davydov

Анатолий Томилов, да, и что? Мы же все равно собираемся создавать std::initializer_list и передавать его в конструктор нашего класса, на важно в какой момент это делать -- внутри функции make_shared или снаружи от нее.

Andrey Davydov
Antervis

во-первых, лучше сразу ориентироваться на std::in_place_t, решающий именно такую задачу для variant/any/optional. Во-вторых, наверно, лучше сделать не только перегрузку make_unique/make_shared, а также перегрузки конструкторов unique_ptr/shared_ptr. По крайней мере с появлением deduction guides потребность в make_фунциях сильно снизилась.

Antervis
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения