Having trouble passing multiple initializer lists

2019-01-27 18:09发布

问题:

I don't understand the error message when trying to pass a variable number of initializer lists:

template<typename... Values>
void foo(Values...)
{
}

int main()
{
    foo(1, 2, 3, "hello", 'a');   // OK

    foo({1}, {2, 3});             // ERROR
}

The error message complains about too many arguments:

prog.cpp: In function ‘int main()’:
prog.cpp:9:20: error: too many arguments to function
                      ‘void foo(Values ...) [with Values = {}]’
     foo({1}, {2, 3});
                    ^
prog.cpp:2:6: note: declared here
 void foo(Values...)
      ^

However, should I not be able to pass as many arguments as I want? [ideone link]

回答1:

The problem is likely deducibility. {} could be uniform initializers to any of the arguments.

This works:

#include <initializer_list>

template<typename... Values>
void foo(std::initializer_list<Values>... args)
{
}

template<typename... Values>
void foo(Values&&... args)
{
}

int main()
{    
    foo(1, 2, 3, "hello", 'a');
    foo({1}, {2, 3});
}

See it Live on Coliru



回答2:

The issue is not with the varadic arguments, but that the compiler cannot deduce the type of a brace enclosed initializer list, except for the case where you've declare the parameter of std::initializer_list<T>

§ 14.8.2.1 Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list for some P0 and the argument is an initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P0 as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (14.8.2.5).

There's even an example right below

template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T


回答3:

The problem is indeed deducibility, as mentioned by other answers. Instead of providing a second function taking an initializer_list, you may specify the type of the argument to foo when calling the function:

#include <initializer_list>

template<typename... Values>
void foo(Values...)
{
}

int main()
{
  foo(1, 2, 3, "hello", 'a');
  foo(std::initializer_list<int>{1}, std::initializer_list<int>{2, 3});
}

Deciding how to treat each parameter, is another question, however.

[EDIT]: Idea is taken from std::shared_ptr and initializer lists



回答4:

This is bad. Consider a simple print() utility:

  template <typename ...Args>
  void print ( Args&&... args) ;

All of this would work:

print("Word", 12, 13.0f, true );

Tuple also works (ignore the implementation required):

auto tup = std::make_tuple("A", true, 42f ) ;    
print("\nTuple I can pass it in ", tup );

But none of this works

print({1,2,3}); // spurious error messages
print({1}, {2}, {3}); // also 
print("\nThe tuple: ", {12, 34, 56 } ) ; //also

The above "solution" does not help too:

template<typename ...Args>
inline  void print(const std::initializer_list<Args>&... il_);

This (as above) does not give usable print() utility :

print("\nMy list is:\t", {1,2,3,4}) ; // error: function print() does not take 2 arguments?

Is it something obvious that is missing here? I would like to mix anything in the call to print() as it's declaration implies.

Anybody?

[Edit 2017-11-08]

Somebody has suggested

 print("\nMy list is:\t", std::initializer_list<int>{1,2,3,4}) ;

And to somewhat remedy the pain of this, I am crushed to admit I have defined this macro "helper"

#define DBJ_IL(T,...) (std::initializer_list<T>{__VA_ARGS__})

Usage:

print("\nMy list is:\t", DBJ_IL(int,1,2,3,4)) ;

But alas, MSVC 14.11.25503 (the latest as of time of this writing) can not compile this. With errors coming from

    1>c:\program files (x86)\microsoft visual 
    studio\2017\community\vc\tools\msvc\14.11.25503\include\utility(415): 
    error C2027: use of undefined type 'std::tuple_size<_Ty>'
   1>        with
   1>        [
   1>            _Ty=std::initializer_list<int>
   1>        ]
   1>c:\program files (x86)\microsoft visual 
      studio\2017\community\vc\tools\msvc\14.11.25503\include\utility(415): 
      note: see declaration of 'std::tuple_size<_Ty>'
   1>        with
   1>        [
   1>            _Ty=std::initializer_list<int>
   1>        ]
   1>c:\program files (x86)\microsoft visual 
    studio\2017\community\vc\tools\msvc\14.11.25503\include\tuple(1051): 
     note: see reference to variable template 'const ::size_t 
     tuple_size_v<std::initializer_list<int> >' being compiled

I am sure nobody want's the rest of the MSVC error dump ... Is it me or is it them?

Doing the print() as generic lambda does not solve anything of course.

/*
forget templates
*/
namespace dbj { namespace {
  auto print = [](auto... param)
  {
   if constexpr (sizeof...(param) > 0) {
    char dummy[sizeof...(param)] = { 
          (( std::cout << param), 0)... 
        };
     }
  };
} }

Even if one passes single and simple init list this wont compile with the same error as above ...

 dbj::print({1,2,3}) ; // msvc compilation error

I know C++17 type deduction of init lists is strengthened and improved, but I can not see in there anything to help me understand is this doable at all?

At last it seems it should be.