无法将大括号初始化列表转换为std tuple。

12
作为一个大型项目的一部分,我正在使用std::tuple和模板;考虑下面的代码:
template <typename ...T> void foo(tuple<T...> t) {}
void bar(tuple<int, char> t) {}
tuple<int, char> quxx() { return {1, 'S'}; }

int main(int argc, char const *argv[])
{
    foo({1, 'S'});           // error
    foo(make_tuple(1, 'S')); // ok
    bar({1, 'S'});           // ok
    quxx();                  // ok
    return 0;
}

根据这个答案,C++17支持从copy-list-initialization进行元组初始化,但似乎此类支持是有限的,因为我在GCC 7.2.0中遇到了以下错误:

main.cpp: In function 'int main(int, const char**)':
main.cpp:14:17: error: could not convert '{1, 'S'}' from '<brace-enclosed initializer list>' to 'std::tuple<>'
     foo({1, 'S'}); // error
                 ^

我能在这种情况下使用大括号语法吗?

一些背景:这将用于重载运算符,所以我想我只能使用元组,并且不能使用可变参数,任何提示都可以接受。

额外信息:Clang 6也会发出警告。

prog.cc:12:5: error: no matching function for call to 'foo'
    foo({1, 'S'});           // error
    ^~~
prog.cc:6:31: note: candidate function [with T = <>] not viable: cannot convert initializer list argument to 'tuple<>'
template <typename ...T> void foo(tuple<T...> t) {}
2个回答

9
一个大括号初始化列表,例如 {1, 'S'} ,实际上并没有类型。在模板推断的上下文中,你只能在特定情况下使用它们——当对 initializer_list<T> 进行推断时(其中 T 是函数模板参数)或者对应的参数已经被其他东西推断时。在这种情况下,这两个条件都不成立——所以编译器无法判断 ... T 应该是什么类型。
所以你可以直接提供类型:
foo<int, char>({1, 'S'});

您可以构造自己的元组并传递给函数:

或者您可以自己构建tuple并将其传递给函数:

foo(std::tuple<int, char>(1, 'S')); // most explicit
foo(std::tuple(1, 'S')); // via class template argument deduction

今天,ClassTemplate<Ts...> 只能从类型为 ClassTemplate<Us...> 或从这种类型继承的类型中推断出。一项假设性提案可以扩展到通过表达式上进行类模板参数推导来尝试执行类模板参数推导,以查看该推导是否成功。在这种情况下,{1,'S'} 不是 tuple<Ts...>,但是 tuple __var{1, 'S'} 成功地推导了 tuple<int,char> ,因此会起作用。这样的提案还必须解决问题,例如……如果我们在推断 ClassTemplate<T,Ts...> 或任何次要变化时,则不允许进行类模板参数推导(但是许多人有时表达了对能够这样做的兴趣)。

我今天不知道这样的提议。


我们能猜测这会改变吗?有没有关于这个的提议,也许是针对C++2a的? - Samuele Pilleri
@SamuelePilleri 不,它不会。 - Barry

2
根据这个答案,C++17支持使用复制列表初始化对元组进行初始化,但似乎此支持是有限的,因为我遇到了以下错误。
问题在于另外一个地方。 当你调用bar({1,'S'})时,编译器知道bar()接收一个tuple<int,char>,所以将1作为int,将'S'作为char。 看另一个例子:如果你定义
void baz (std::tuple<int> const &)
 { }

您可以调用

baz(1);

因为编译器知道baz()接收一个std::tuple<int>,所以使用1来初始化元组中的int

但是如果使用

template <typename ...T>
void foo(tuple<T...> t)
 { }

编译器不知道 T... 类型;当您调用时。
foo({1, 'S'}); 

编译器应该推导哪些类型的T...

我至少看到两种假设:T = int,charT = std::pair<int,char>;或者T = std::tuple<int,char>

编译器应该遵循哪种假设呢?

我的意思是:如果您将std::tuple传递给foo(),则编译器接受元组中类型列表作为T...列表;但是如果您传递其他内容,则编译器必须推断出正确的std::tuple;但是在这种情况下,这种推断并不是唯一的。所以出错了。


这就是骗了我的地方!我的意思是,foo期望一个std::tuple,应该非常简单。 - Samuele Pilleri
@SamuelePilleri - 如果你传递一个 std::tuple,它就可以工作;但如果你传递其他东西,编译器必须推断出正确的元组;但在这种情况下,这种推断是不唯一的。所以会出现错误。 - max66
1
它不能使用类模板参数推导相同的规则吗? - Samuele Pilleri
1
@SamuelePilleri - 我不知道,我想肯定会有一些问题。无论如何,从 std::tuple t({1, 'S'}) ,应该推断出哪个 std::tuple 类型呢?是 std::tuple<std::pair<int, char>> 吗?还是 std::tuple<std::tuple<int, char>>?问题在于从 {1, 'S'} 无法推断出唯一类型。 - max66
@max66 假设我们有这个规则,那么我们就不会随意插入括号了吧? - Barry
显示剩余5条评论

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