Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expanding a lambda for each parameter of a parameter pack: Clang vs. GCC

This code works fine in Clang 3.5:

#include <iostream>
#include <string>

void callFuncs() {}

template<typename Func, typename ...Funcs>
void callFuncs(const Func &func, const Funcs &...funcs)
{
    func();
    callFuncs(funcs...);
}

template<typename ...Types>
void callPrintFuncs()
{
    callFuncs(([] { std::cout << Types() << std::endl; })...);
}

int main()
{
    callPrintFuncs<int, float, double, std::string>();
}

However, in GCC 4.9, I get the following error instead:

test.cpp: In lambda function:
test.cpp:16:54: error: parameter packs not expanded with '...':
     callFuncs(([] { std::cout << Types() << std::endl; })...);
                                                      ^
test.cpp:16:54: note:         'Types'
test.cpp: In function 'void callPrintFuncs()':
test.cpp:16:58: error: expansion pattern '<lambda>' contains no argument packs
     callFuncs(([] { std::cout << Types() << std::endl; })...);

So, which compiler has a bug, Clang or GCC? The Clang behavior makes the most sense to me at least.

like image 735
Emil Eriksson Avatar asked Feb 01 '15 18:02

Emil Eriksson


1 Answers

gcc is broken here. There are rules against unexpanded parameter packs in the standard, but the above parameter pack is expanded.

It is expanded after the end of innermost statement it is in, but the standard does not require that the parameter packs be expanded by the end of every statement.

The fact that gcc got it wrong is sort of understandable; naively, you'd think that a parameter pack can only be within one statement, and the failure to expand at the end of the statement is fatal. But lambdas let you nest statements within statements.

A general workaround can be to pass in one lambda and pass in a "tag" type to it.

template<class T>struct tag_t{using type=T;};
template<class Tag>using type_t=typename Tag::type;

template<typename Func, typename ...Ts>
void callOnEachOf(Func&&func, Ts&&...ts)
{
  using discard=int[];
  (void)discard{0,((void)(
    func(std::forward<Ts>(ts))
  ),0)...};
}
template<typename ...Types>
void callPrintFuncs()
{
  callOnEachOf(
    [](auto tag){
      using Type=type_t<decltype(tag)>;
      std::cout << Type() << std::endl;
    },
    tag_t<Types>...
  );
}
like image 99
Yakk - Adam Nevraumont Avatar answered Nov 09 '22 19:11

Yakk - Adam Nevraumont