Возможность явного выбора конструктора в функциях типа "make_<что-то-там>"

dmitriy@izvolov.ru
dmitriy@izvolov.ru

Проблема

 

Всю соль проблемы показывает функция std::make_from_tuple.

 

Случай первый: тривиальный тип.

struct trivial
{
    int x;
    double y;
};

make_from_tuple<trivial>(make_tuple(1, 3.14)); // Ошибка компиляции.

Пример показывает, что данный случай просто не покрывается текущей редакцией стандартной библиотеки. Тривиальный тип создать из кортежа при помощи функции std::make_from_tuple невозможно.

 

Случай второй: наличие разных вариантов конструктора.

Для простоты возьмём std::vector.

make_from_tuple<vector<size_t>>(make_tuple(5, 1));
// Какой вектор должен быть создан?
// [1, 1, 1, 1, 1] или [5, 1]?

В текущей редакции всегда будет вызван первый вариант, то есть мы получим пять единичек.

И нет принципиальной возможности вызвать второй вариант.

 

Решение

 

Суть решения чрезвычайно проста — она состоит в использовании перегрузки по тегам.

Создаём два тега, каждый из которых будет сигнализировать о том, что нужно позвать либо круглые, либо фигурные скобки соответственно (имена черновые):

template <typename T>
struct parens_t {};

template <typename T>
constexpr auto parens = parens_t<T>{};

template <typename T>
struct braces_t {};

template <typename T>
constexpr auto braces = braces_t<T>{};

Получаем:

auto a = make_unique(parens<A>, 1, 3); // A(1, 3)
auto b = make_unique(braces<A>, 1, 3); // A{1, 3}

auto c = make_from_tuple(parens<A>, std::make_tuple(100, 3.14)); // A(100, 3.14)
auto d = make_from_tuple(braces<A>, std::make_tuple(100, 3.14)); // A{100, 3.14}
// Раньше фигурные скобки вообще нельзя было позвать. Никак. Совсем.
// Теперь можно.

Также обращаю внимание на то, что при использовании такого тега тип объекта выводится из тега, то есть его не надо указывать явно:

// Раньше:
auto a = make_shared<very_very_very_long_type_name>(very_very_very_long_type_name{1, 3});

// Теперь:
auto b = make_shared(braces<very_very_very_long_type_name>, 1, 3);


Все старые перегрузки, естественно, остаются. Просто теперь есть возможность явного выбора способа инициализации.

 

Аналогично это будет работать и с функциями типа emplace_back, которые также принимают набор параметров, которые нужно передать конструктору объекта.

 

Пример для подражания

 

Такой подход для стандартной библиотеки не нов. Похожая схема применяется с тегом std::in_place_type, например, при конструировании класса std::variant. Данный тег сигнализирует о том, что нужно позвать конструктор типа. Но в нашем случае мы идём чуть дальше, и даём возможность не просто просигнализировать о том, что нужно позвать конструктор, но и сообщаем какой именно это должен быть конструктор.

 

Итог

 

 

 

Данное решение:

0. Упрощает жизнь и открывает новые возможности пользователю стандартной библиотеки.

1. Полностью сохраняюет обратную совместимость. Ничего старого не ломается.

2. Достаточно общее. Работает как с make_shared, make_unique, make_any и иже с ними, так и с make_from_tuple.

3. Органично вливается в стандартную библиотеку, удобно в использовании и (возможно, субъективно) красивое.

10
рейтинг
6 комментариев
Anatoly Scheglov
Во всех примерах только make_from_tuple.
То что make_from_tuple не проверяет отсутствие пользовательского конструктора - это проблема ее реализации.
Кортеж это не массив, поэтому в примере с vector было бы странно получить вектор {4,1} из кортежа.
Anatoly Scheglov
dmitriy@izvolov.ru
Anatoly Scheglov,

> Во всех примерах только make_from_tuple.
Враньё.

> То что make_from_tuple не проверяет отсутствие пользовательского конструктора - это проблема ее реализации.

Проблема функции make_from_tuple в том, что с её помощью принципиально невозможно проинициализировать объект при помощи фигурных скобок.

> Кортеж это не массив, поэтому в примере с vector было бы странно получить вектор {4,1} из кортежа.

Во-первых, std::vector здесь приведён для простоты. Все знают, что у него есть и такой, и сякой конструкторы. Благодаря этому не нужно вставлять в пример самопальные классы.
Во-вторых, "странность" — понятие субъективное. Если пользователю нужно позвать именно такой конструктор именно с этими значениями кортежа, то он имеет на это право. Если типы не сойдутся, то компилятор подскажет, что не так.

Так что критика не принимается.
Ещё раз внимательно перечитайте предложение.
dmitriy@izvolov.ru
Victor Dyachenko
А чем перегрузка функций по тэгу лучше просто двух функций? Вроде

make_from_tuple_parens()
make_from_tuple_braces()

Подобные тэги нужны только в конструкторах, потому как имя там выбрать нельзя.

А по поводу невозможности использовать emplace()/make_unique() для типов без конструктора, вроде обычных структур, так на это уже много лет как заведён дефект в Комитете, и даже решение уже есть, но вот что-то так оно и лежит без движения.
Victor Dyachenko
dmitriy@izvolov.ru
Victor Dyachenko,

> А чем перегрузка функций по тэгу лучше просто двух функций?

Конкретно в случае с make_from_tuple я бы предпочёл вообще другое решение. Подробнее об этом я писал здесь: groups.google.com/a/isocpp.org/forum/?fromgroups#!topic/std-discussion/aQQzL0JoXLg

Ну а в целом, кажется, что это покрасивее и поуниверсальнее. Потому что для каждой функции делать ещё по два варианта с какими-то суффиксами — не очень хорошо. К тому же теги можно использовать в связке с метапрограммированием.

> решение уже есть

Если речь идёт об этом: cplusplus.github.io/LWG/lwg-active.html#2089 , то это полумера. Это решение не подразумевает возможности явного выбора способа инициализации. А я считаю, что такая возможность всё-таки нужна.
dmitriy@izvolov.ru
yndx-antoshkka
Если у вас есть силы дальше работать над идеей, могу помочь с написанием proposal и обсуждениями в международной рассылке.
yndx-antoshkka
dmitriy@izvolov.ru
yndx-antoshkka,

Создал тему на форуме: groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/Wnl9w53uAiY
dmitriy@izvolov.ru
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).