Vittorio Romeo Vittorio Romeo - 15 days ago 9
C++ Question

Expanding parameter pack into lambda with fold expression - gcc vs clang

Considering the following code snippet:

template <typename TF>
void post(TF){ }

template <typename... TFs>
struct funcs : TFs...
{
funcs(TFs... fs) : TFs{fs}... { }

void call()
{
(post([&]{ static_cast<TFs&>(*this)(); }), ...);
}
};


clang++ 3.8+ successfully compiles the code.

g++ 7.0 fails to compile with the following error:

prog.cc: In lambda function:
prog.cc:10:43: error: parameter packs not expanded with '...':
(post([&]{ static_cast<TFs&>(*this)(); }), ...);
~~~~~~~~~~~~~~~~~~~~~~~~^~
prog.cc:10:43: note: 'TFs'
prog.cc: In member function 'void funcs<TFs>::call()':
prog.cc:10:13: error: operand of fold expression has no unexpanded parameter packs
(post([&]{ static_cast<TFs&>(*this)(); }), ...);
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Removing the
post
call and the lambda makes g++ compile the fold expression.

Is this interaction between lambdas, fold expressions and template function calls somehow forbidden by the standard, or is this a gcc bug?

Answer

This is an old gcc bug. It is one of the few cases where gcc's template handling is worse than MSVC. Shame gcc. Shame.

A workaround that sometimes works is to use tags and pack expansion.

template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
template<class Tag>using type_t=typename Tag::type;
#define TAG2TYPE(...) type_t<decltype(__VA_ARGS__)>

// takes args...
// returns a function object that takes a function object f
// and invokes f, each time passing it one of the args...
template<class...Args>
auto expand( Args&&...args ) {
  return [&](auto&& f)->decltype(auto) {
    using discard=int[];
    (void)discard{0,(void(
      f( std::forward<Args>(args) )
    ),0)...};
  };
}

template <typename TF>
void post(TF){ }

template <typename... TFs>
struct funcs : TFs...
{
  funcs(TFs... fs) : TFs{fs}... { }

  void call()  { 
    expand( tag<TFs>... )
    ([&](auto tag){
      post(static_cast< TAG2TYPE(tag)& >(*this)());
    });
  }
};

where we carefully avoid expanding over the end of a lambda by passing the lambda in each time. Instead, we take a set of arguments and expand it into a set of lambda calls.

The lambda gets the types passed in as a tag, then we convert it back to a type.

Live example

Do not store return type of expand if you passed it temporaries.