28

Case 1:

Consider the following pack expansion in lambda noexcept specifier:

template <bool... B> 
auto g() {  
  ([]() noexcept(B) {}, ...);  
}

Clang and MSVC accept this code, but GCC rejects with:

error: expansion pattern '<lambda>' contains no parameter packs

Is this a valid code? Which Compiler should I trust?

Case 2:

Consider the following pack expansion in lambda requires-clause:

template <bool... B> 
auto g() {  
  ([](auto) requires(B) {}, ...);  
}

In this case, Clang and MSVC still accept this code, and GCC rejects it with the same error message. Is this just the same bug?

Case 3:

Consider the following pack expansion in the lambda template list:

template <typename... Args> 
void g(Args...) {
  ([]<Args>(){}, ...);  
}

This time three compiler all reject with the same error message:

expansion pattern '<lambda>' contains no parameter packs

Is there a difference compared to case 1? Or is this a common bug?


Update:

GCC fixed case 1 in 99584, and MSVC fixed case 3 in this.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 5
    All 3 of those _should_ be accepted; if they don't, I'd consider it to be a bug in the standard. They are each expressions, those expressions (at some level of recursion) contain a parameter pack, and those packs once expanded make sense. However, specifying how parameter packs get expanded is a pain, and I can believe there are bugs in the standard that mandate them not working. (of course, the `f(...)` call may be UB (or maybe not, I'd have to look at how empty lambdas and `...` interact), but that isn't important) – Yakk - Adam Nevraumont Mar 14 '21 at 14:50
  • 1
    @Yakk-AdamNevraumont - Are you sure (that is correct) about the third case? I mean: what is `[](){}`? Shouldn't be `[](){}` that, in that case, define a new typename template parameter, shadows the function `Args` list and can't be unpacked? – max66 Mar 14 '21 at 15:06
  • 3
    @max66 non type template parameters – Yakk - Adam Nevraumont Mar 14 '21 at 15:06
  • @Yakk-AdamNevraumont - Uh... you're right. – max66 Mar 14 '21 at 15:08
  • 1
    Status update for this interesting question: **1**) Fixed in g++ 11.1. **2**) Not fixed in g++ 11.1, so not the same bug as in 1. **3**) Fixed in MSVC 19.29. – Ted Lyngmo Jul 05 '21 at 19:52

1 Answers1

3

Interesting exercise! Looking at the C++20 draft, the standard first distinguishes generic lambdas (7.5.5.5):

A lambda is a generic lambda if the lambda-expression has any generic parameter type placeholders (9.2.8.5), or if the lambda has a template-parameter-list.

int i = [](int i, auto a) { return i; }(3, 4); // OK: a generic lambda
int j = []<class T>(T t, int i) { return i; }(3, 4); // OK: a generic lambda

Therefore, case 1 has non-generic lambdas and 2 and 3 have generic lambdas. It does not differentiate between type and non-type template parameters, so I think I agree with Yakk - Adam Nevraumont, all three should be admissible.

Lambdas are represented by an unnamed, unique closure type having a function call operator (operator()) (template in case of a generic lambda), whose signature and constraints are determined by the lambda expression. The compiler could definitely satisfy each case with code resembling the following:

Case 1

template<bool B>
struct closure {
    auto operator()() const noexcept(B) {}
};

template <bool... B> 
auto g() {  
  (closure<B>{}, ...);  
}

Case 2

template<bool B>
struct closure {
    auto operator()(auto) const requires(B) {}
};

template <bool... B> 
auto g() {  
  (closure<B>{}, ...);  
}

Case 3

template<typename T>
struct closure {
    template<T>
    auto operator()() const {}
};

template <typename... Args> 
void g(Args...) {
  (closure<Args>{}, ...);  
}

These all compile on my machine, currently with gcc 11.2.1.

sigma
  • 2,758
  • 1
  • 14
  • 18