c++ parameter pack expansion with concepts
Asked Answered
H

1

6

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;
}
Hypothermia answered 16/1, 2023 at 11:0 Comment(2)
The #1 form introduces a new parameter pack, whereas #2 expands an already existing pack.Puffer
A concept itself cannot represent a type, only constraints on a type. Not the the whole body of nth_element can be replaced by return std::get<N>(std::tuple{args...});.Ostrom
C
3

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 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>());
}
Centring answered 18/1, 2023 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.