Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameter pack expansion with lambda in C++20

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.

like image 208
康桓瑋 Avatar asked Mar 14 '21 13:03

康桓瑋


1 Answers

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.

like image 125
sigma Avatar answered Nov 15 '22 07:11

sigma