12

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) {}
max66
  • 65,235
  • 10
  • 71
  • 111
Samuele Pilleri
  • 734
  • 1
  • 7
  • 17

2 Answers2

9

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
2

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • That's what tricked me! I mean, `foo` expects a `std::tuple`, it should be pretty straightforward. – Samuele Pilleri Apr 25 '18 at 21:12
  • @SamuelePilleri - And if you pass a `std::tuple`, it works; but if you pass something else, the compiler must deduce the correct tuple; but this deduction, in this case, is not unique. So the error. – max66 Apr 25 '18 at 21:16
  • 1
    Couldn't it use the same rules as class template argument deduction? – Samuele Pilleri Apr 25 '18 at 21:22
  • 1
    @SamuelePilleri - I don't know; I suppose will be some problems; anyway, from `std::tuple t({1, 'S'})`, which `std::tuple` should be deduced? `std::tuple>`? Or `std::tuple>`? Remain the problem that from `{1, 'S'}` you can't deduce a unique type. – max66 Apr 25 '18 at 21:32
  • @max66 Presumably if we had this rule, we wouldn't arbitrarily insert parentheses? – Barry Apr 25 '18 at 21:36
  • @max66 Totally agree, but since both `tuple x(1, 'S')` and `tuple x{1, 'S'}` work I was wondering "why not?". Anyway, thank you both for your clarifications. – Samuele Pilleri Apr 25 '18 at 21:38
  • @Barry - do you mean that something as `foo({1, {'S', 2L}})` could add a recursive ambiguity? – max66 Apr 25 '18 at 22:02
  • @max66 I don't know why "ambiguity" - the only way that could possibly work is if we just decided that braced-init-lists were `tuple`s. – Barry Apr 25 '18 at 23:15
  • @Barry - so from `{1, {'S', 2L}}` we could deduce `std::tuple>`? Anyway, I suppose it could be possible: given that the target is `std::tuple`, initializing with `{as...}` we could give a preferred deduction for `std::tuple`; and if we want `std::tuple>` we could explicit with `foo>({1, 'S'})` or with `foo(std::make_pair(1, 'S'))`. But there isn't a risk that introducing this rule some old code change behavior? – max66 Apr 26 '18 at 00:34
  • I'm not sure why you keep bringing up `pair`, it is not relevant here. While it *may* be reasonable to allow deduction of `tuple` from `{1,'S'}`, you at least have `tuple` there to guide your deduction. How would you determine that the inner braced-init-list needed to *also* be a `tuple`? You have nothing to guide you there at all. I don't see how just assuming that we repeat the outer class template is reasonable. – Barry Apr 26 '18 at 03:40