`std::make_tuple` 的原因是什么?

43
我是说为什么存在 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

但是就像上面所说的,对于许多其他类模板,您并没有这样的功能。


3
有多种类似的函数,但最明显的直接比较是std::make_pair。我认为一个经验法则可能是,如果你想要构造这些对象只是作为另一个表达式的一部分、调用函数等,则它们存在。 - BoBTFish
1
你想为哪些其他类编写 make_* 函数呢? 目前我们已经有了 make_shared,make_unique 等等。 - Marc Glisse
1
唯一的区别在于类型推断。但是类型推断可以很有用,这就是为什么。 - Nathan Cooper
5
并非所有类型都能被命名,例如:make_tuple([&](){ f(x); }, std::mem_fn(&X::foo)) - Kerrek SB
3
make_tuple 属于 make_tupletieforward_as_tuple 这个系列,它们分别返回 prvalue、lvalue 和转发值。 - Kerrek SB
显示剩余4条评论
4个回答

47
因为无法使用参数推导来构造函数,所以需要显式地写出 std::tuple<int, double>(i,d);
这样做可以更方便地创建元组并将其一次性传递给另一个函数。 takes_tuple(make_tuple(i,d))takes_tuple(tuple<int,double>(i,d)) 相比,少了一个需要更改的地方,特别是当 id 的类型发生变化时,尤其是在旧类型和新类型之间存在可能的转换时。
如果可以编写 std::tuple(i,d);,那么 make_* 就可能会变得多余。 (不要问为什么。也许就像语法 A a(); 不会调用默认构造函数一样。C++有一些痛苦的语法特性。) 更新说明: 正如Daniel正确指出的那样,C++17将被增强,以使模板参数推导对于构造函数可以工作,因此这种委托将过时。

16
C++17 版本起,可以使用 类模板推导(class template deduction),也就是可以写出 std::tuple t(1, 1.0, 'a'); 或者 auto t = std::tuple(1, 1.0, 'a'); 等等。详见此处 here - Daniel Langr
将您的更新说明包含在答案中。感谢您的呼叫。 - luk32
5
我不同意这种委托将变得过时的说法。如果您在std::ref()调用中包装某些内容,std::make_tuple将使用X&,这在C++17中仍然很有用,因为用于std::tuple推导指南似乎不能处理这种情况。 - Dev Null

9

我们可以在建议N3602:构造函数的模板参数推导中找到为什么我们需要make_tuple和其他各种make_*实用程序的理由,该建议指出(重点是我的):

This paper proposes extending template parameter deduction for functions to constructors of template classes. The clearest way to describe the problem and solution is with some examples.

Suppose we have defined the following.

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;
    };

Currently, if we want to instantiate template classes, we need to either specify the template parameters or use a "make_*" wrapper, leverage template parameter deduction for functions, or punt completely:

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问题60进行跟踪。

@black 嗯,我刚刚在我的回答中添加的EWG问题表明存在一些问题,但是这项工作得到了鼓励。 EWG活动列表尚未从Kona更新,因此可能已经发生了变化。 - Shafik Yaghmour

4

我认为这种类型的函数聪明的运用是将其作为参数传递。
就像这样:

std::bind_front(&std::make_tuple, 1, "test", true);

如果我没记错的话,这会很有用,因为我们不能直接调用构造函数。

auto obj = Object::Object(3); // error: cannot call constructor ‘Object::Object’ directly [-fpermissive]

2

仅用于模板参数推导。然而,这里有一个(人为制造的)例子,在使用lambda时需要这样做:

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; })无法使用,因为这两个lambda表达式具有不同的类型。像std::function这样的多态包装器会增加运行时开销。一个具有用户定义的operator ()的命名类将起作用(根据操作符的内容,它可能还需要是B的友元)。这就是在C++11之前我们使用的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接