Could not convert from brace-enclosed initializer

2020-07-07 11:27发布

As part of a bigger project, I'm playing with std::tuple and templates; consider the following code:

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

According to this answer C++17 supports tuple initialization from copy-list-initialization, however it seems such support is limited since I get the following error (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
                 ^

Is there any way I can use brace-enclosed syntax in this scenario?

Some Context : this is going to be used in an operator overload so I guess I'm bound to tuples and cannot make use of variadics, any hint is well-accepted.

Extra : Clang 6 also complains

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条回答
女痞
2楼-- · 2020-07-07 12:08

According to this answer C++17 supports tuple initialization from copy-list-initialization, however it seems such support is limited since I get the following error

The problem is another.

When you call bar({1, 'S'}), the compiler knows that bar() receive a tuple<int, char>, so take 1 as int and 'S' as char.

See another example: if you define

void baz (std::tuple<int> const &)
 { }

you can call

baz(1);

because the compiler knows that baz() receive a std::tuple<int> so take 1 to initialize the int in the tuple.

But with

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

the compiler doesn't know the T... types; when you call

foo({1, 'S'}); 

what T... types should deduce the compiler?

I see, at least, two hypothesis: T = int, char or T = std::pair<int, char>; or also T = std::tuple<int, char>.

Which hypothesis should follows the compiler?

I mean: if you pass a std::tuple to foo(), the compiler accept the list of types in the tuple as the list of T...; but if you pass something else, the compiler must deduce the correct std::tuple; but this deduction, in this case, is not unique. So the error.

查看更多
淡お忘
3楼-- · 2020-07-07 12:09

A braced-init-list, like {1, 'S'}, does not actually have a type. In the context of template deduction, you can only use them in certain cases - when deducing against initializer_list<T> (where T is a function template parameter) or when the corresponding parameter is already deduced by something else. In this case, neither of those two things is true - so the compiler cannot figure out what ...T is supposed to be.

So you can provide the types directly:

foo<int, char>({1, 'S'});

Or you can construct the tuple yourself and pass that in:

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

Today, ClassTemplate<Ts...> can only be deduced from expressions of type ClassTemplate<Us...> or types that inherit from something like that. A hypothetical proposal could extend that to additionally try to perform class template argument deduction on the expression to see if that deduction succeeds. In this case, {1, 'S'} isn't a tuple<Ts...> but tuple __var{1, 'S'} does successfully deduce tuple<int, char> so that would work. Such a proposal would also have to address issues like... what if we're deducing ClassTemplate<T, Ts...> or any minor variation, which isn't something that class template argument deduction allows (but is something that many people have at times expressed interest in being able to do).

I'm not aware of such a proposal today.

查看更多
登录 后发表回答