Home > database >  c parameter pack expansion with concepts
c parameter pack expansion with concepts

Time:01-19

Why I get a compilation error if uncomment line #1 and comment line #2? Demo: https://godbolt.org/z/KW6dhsrKd

#include <utility>

template <typename, std::size_t> concept prefix = true;

template<std::size_t>
struct dummy { template<typename T> constexpr dummy(T){}; };

template <auto N>
consteval auto nth_element(auto... args) 
{
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
    //return [](prefix<Is> auto..., auto arg, auto...) { //compile error // #1
    return [](dummy<Is> ..., auto arg, auto...) { // OK                  // #2
    return arg;
    }(args...);
}(std::make_index_sequence<N>());
}

int main()
{
    static_assert(nth_element<0>(1, 2, 3) == 1);
    static_assert(nth_element<1>(1, 2, 3) == 2);
    static_assert(nth_element<2>(1, 2, 3) == 3);
    
    return 0;
}

CodePudding user response:

From [dcl.fct]/22:

An abbreviated function template is equivalent to a function template ([temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. [..] The invented type template-parameter is a template parameter pack if the corresponding parameter-declaration declares a function parameter pack.

Note that prefix<Is> auto... is both a pack and a pack expansion simultaneously.
So the transformed template would look like

[] <prefix<Is>... Args> (Args auto..., auto arg, auto...) {
    return arg;
}(args...)

Which is equivalent to

[] <typename... Args> requires(prefix<Args, Is> && ...) (Args..., auto arg, auto...) {
    return arg;
}(args...)

(This still generates the same error message, too.)
Where we can see that 1) Args is in a non-deduced context and will always remain empty, and 2) Is and Args would have to agree in length for the constraint to be satisfiable.

The solution given in the comments is not ideal IMO, as creating a tuple is likely relatively expensive (especially at compile time). I think your solution proper forwarding is the best for now (until we get proper pack subscripts). I.e. something like this:

template<std::size_t>
struct dummy { constexpr dummy(auto&&){}; };

template <std::size_t N, typename... Args>
constexpr decltype(auto) nth_element(Args&&... args) {
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) -> decltype(auto) {
        return [](dummy<Is> ..., auto&& arg, auto&&...) -> decltype(auto) { // OK                  // #2
            return std::forward<decltype(arg)>(arg);
        }(std::forward<Args>(args)...);
    }(std::make_index_sequence<N>());
}
  • Related