using c++20 concepts to find nth element of parameter pack

124 Views Asked by At

I am looking for a non-recursive, modern method (which compiles, at least with gcc and clang) for finding the th element of a variadic parameter pack. Indeed, it would seem such a solution, using c++20 concepts, exists: Kris Jusiak gave a talk nth pack element at CppCon, CppNow, and Meeting Cpp, all 2022 and all found on YouTube; Alex Dathskovsky wrote a LinkedIN post Nth element variadic pack extraction (https://www.linkedin.com/pulse/nth-element-variadic-pack-extraction-alex-dathskovsky-); and surely there are other places this may be found. Both solutions are fairly similar, both purportedly perform very well; I will include the former below. Sadly neither of the mentioned examples compiles. Here is the compiler explorer link given in Jusiak's talk (https://godbolt.org/z/WPhe5MerW), and below is a copy:

#include <cstddef> // adding #include <concepts> changes nothing
#include <utility>

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

template <auto N>
constexpr auto nth_element(auto... args) {
  return [&]<auto... Is>(std::index_sequence<Is...>) {
    return [] (prefix<Is> auto..., auto arg, auto...) {
      return arg;
    }(args...);
  }(std::make_index_sequence<N>());
}

static_assert(1 == nth_element<0>(1));
static_assert(1 == nth_element<0>(1, 2));
static_assert(2 == nth_element<1>(1, 2));        // this line throws an error
static_assert(1 == nth_element<0>(1, 2, 3));
static_assert(2 == nth_element<1>(1, 2, 3));     // and this one
static_assert(3 == nth_element<2>(1, 2, 3));     // and this one

This doesn't compile locally, nor on godbolt. Comments are my own. Here's what gccs complaints boil down to:

<source>:10:27: error: mismatched argument pack lengths while expanding 'prefix<auto:2, Is>'
   9 |     return [] (prefix<Is> auto..., auto arg, auto...) {

and clangs:

<source>:9:31: note: because substituted constraint expression is ill-formed: pack expansion
contains parameter packs 'auto:1' and 'Is' that have different lengths (0 vs. 1)
    9 |     return [] (prefix<Is> auto..., auto arg, auto...) {

This seems to be causing a "constraints not satisfied" so that the template argument deduction/substitution fails, leading to no matching (function) call for the nested lambda in line 9.

Well I tried searching around, to no avail. Not sure if it is because disclaimer I'm not a professional, or if it's truly a bit tricky! I do not know what to do. I tried silly things like adding and removing ellipsis here and there, like std::size_t... in the prefix concept, or prefix<Is...> in our problematic lambda, but neither helped. The LinkedIN post and the conference talk try to break it down as well, which I hoped to digest enough to be able to come up with a fix – not the case. Sincerely I apologize for not having a better attempt to offer. Yes, other working solutions exist and some may be found in the aforementioned sources, but I am particularly interested in getting one of these which uses concepts to compile.

I also found some potentially useful and relevant SO posts, none of which I could figure out how to use to help. Many offer recursion as the solution.

  • Mismatched argument pack lengths while expanding std::index_sequence and variadic argument pack (see questions/74805917/mismatched-argument-pack-lengths-while-expanding-stdindex-sequence-and-variadi)
  • How to Iterate over a parameter pack (see questions/71985500/how-to-iterate-over-a-parameter-pack)
  • How can I iterate over a packed variadic template argument list? questions/7230621/how-can-i-iterate-over-a-packed-variadic-template-argument-list
  • template parameter packs access Nth type and Nth element (see questions/20162903/template-parameter-packs-access-nth-type-and-nth-element)
  • C++: candidate template ignored when iterating over tuple (see questions/68299663/c-candidate-template-ignored-when-iterating-over-tuple)

Any guidance or help with this would be greatly appreciated!


Now, perhaps this post is too long by now, and the following may be more appropriate as a (separate) follow-up question, but in Andrei Alexandrescu's CppCon 2021 talk Embracing (and also Destroying) Variant Types Safely (found on YouTube) he suggests adding the following to the standard for help in dealing with parameter packs:

template<typename... Ts> struct type_sequence {};
//
template<typename... Ts> struct head;
template<typename T, typename... Ts>
struct head<type_sequence<T, Ts...>>
{
  using type = T;
};
template<typename T>
using head_t = typename head<T>::type;
//
template<typename... Ts> struct tail;
template<typename T, typename... Ts>
struct tail<type_sequence<T, Ts...>>
{
  using type = type_sequence<Ts...>;
};
template<typename T>
using tail_t = typename tail<T>::type;
//
template<typename T, typename List> struct cons;
template<typename T, typename... Ts>
struct cons<T, type_sequence<Ts...>>
{
  using type = type_sequence<T, Ts...>;
};
template<typename T, typename List>
using cons_t = typename cons<T, List>::type;

and I wondered if in the concept-expansion solution for finding the th element in a pack, we might be able to find a way to replace std::index_sequence with this type_sequence. Thank you in advance!

1

There are 1 best solutions below

0
On

This much simpler function satisfies all your tests shown (and also a test with hybrid types). What I cannot tell you is if this meets your compile time requirements for speed (I assume the standard library implementers will have done a good enough job)

"concepts" do not even come into play at all since there is no need to constrain the template in any way.

#include <tuple>

template <std::size_t N>
constexpr auto nth_element(auto... args)
{
    return std::get<N>(std::forward_as_tuple(args...));
}

int main()
{
    static_assert(1 == nth_element<0>(1));
    static_assert(1 == nth_element<0>(1, 2));
    static_assert(2 == nth_element<1>(1, 2));        // this line throws an error, ok now
    static_assert(1 == nth_element<0>(1, "Hello", 3.0));
    static_assert(2 == nth_element<1>(1, 2, 3));     // and this one, ok now
    static_assert(3 == nth_element<2>(1, 2, 3));     // and this one, ok now
}