В чем причина `std:: make_tuple`?

Я имею в виду, почему существует std::make_tuple? Я знаю, что есть ситуации, когда функция уменьшает количество символов, которые вы набираете, потому что вы можете избежать параметров шаблона. Но это единственная причина? Что делает std::tuple особенным, что функция существует, в то время как другие шаблоны классов не имеют такой функции? Это только потому, что вы можете чаще использовать std::tuple в таких ситуациях?


Вот два примера, где std::make_tuple уменьшает количество символов:

// Avoiding template parameters in definition of variable.
// Consider that template parameters can be very long sometimes.
std::tuple<int, double> t(0, 0.0); // without std::make_tuple
auto t = std::make_tuple(0, 0.0);  // with std::make_tuple

// Avoiding template parameters at construction.
f(std::tuple<int, double>(0, 0.0)); // without std::make_tuple
f(std::make_tuple(0, 0.0));         // with std::make_tuple

Но, как написано выше, у вас нет такой функции для многих других шаблонов классов.

Ответы

Ответ 1

Потому что вы не можете использовать вывод аргумента для конструкторов. Вам нужно явно написать std::tuple<int, double>(i,d);.

Это делает его более удобным для создания кортежа и передачи его другой функции в один кадр.

takes_tuple(make_tuple(i,d)) vs takes_tuple(tuple<int,double>(i,d)).

Уменьшение места при изменении типа i или d, особенно если возможны переходы между старым и новым типами.

Если бы можно было написать std::tuple(i,d);, make_* было бы (вероятно) избыточным.

(Не спрашивайте, почему здесь. Возможно, по тем же причинам, почему синтаксис A a(); не вызывает конструктор по умолчанию. Существуют некоторые болезненные особенности синтаксиса С++.)

ОБНОВЛЕНИЕ ПРИМЕЧАНИЕ: Поскольку Daniel правильно распознает, С++ 17 будет расширен, поэтому вывод шаблона будет работать для конструкторов, и такое делегирование устареет.

Ответ 2

Мы можем найти обоснование того, зачем нам нужны make_tuple и другие другие make_ * утилиты в предложении N3602: вычитание параметров шаблона для конструкторов в котором говорится (акцент мой):

В этом документе предлагается расширить вычет параметра шаблона для функций конструкторам шаблонных классов. Самый простой способ описать проблему и решение - это некоторые примеры.

Предположим, что мы определили следующее.

vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 }; 
vector<int> vi2; template<class Func> 
    class Foo() { 
        public: Foo(Func f) : func(f) {} 
        void operator()(int i) { os << "Calling with " << i << endl; f(i); } 
        private: 
        Func func;
    };

В настоящее время, если мы хотим создать экземпляры классов шаблонов, нам нужно указать параметры шаблона или использовать "make_ *" оболочку, вывести аргумент шаблона шаблона для функций или полностью punt:

pair<int, double> p(2, 4.5); 
auto t = make_tuple(4, 3, 2.5); 
copy_n(vi1, 3, back_inserter(vi2)); // Virtually impossible to pass a lambda to a template class' constructor
for_each(vi.begin(), vi.end(), Foo<???>([&](int i) { ...}));

Примечание. Предложение отслеживается с помощью EWG issue 60.

Ответ 3

Только для вывода аргумента шаблона. Однако здесь (надуманный) пример, где это необходимо для использования лямбда:

class A
{
public:
    template<typename F>
    A(const std::tuple<F> &t)
    {
        // e.g.
        std::get<0>(t)();
    }
};

class B : public A
{
public:
     B(int i) : A(std::make_tuple([&i]{ ++i; }))
     {
         // Do something with i
     }
};

std::tuple<decltype([&i]{ ++i; })>([&i]{ ++i; }) не может использоваться, потому что два лямбда-выражения имеют разные типы. Полиморфная оболочка, такая как std::function, добавляет служебные данные во время выполнения. Будет работать именованный класс с пользовательским operator () (который также может быть другом B, в зависимости от содержимого тела оператора). Это то, что мы использовали в древние времена до С++ 11.