nyarlathotep108
nyarlathotep108

Reputation: 5523

C++20 Concept to check tuple-like types

I'd like to create a concept for tuple-like types. A tuple-like type would be something that, like std::pair, std::tuple and std::array, offers a compile time known number of types accessible through a compile time indexed get<size> function.

I am new to concepts and I do not know where to start. Unfortunately, the STL seems not to have such a concept in the <concepts> header.

For example, I can write:

template<typename T>
concept tuple_like = requires(T value)
{
    std::invoke(get<0>, value);
};

But I am not sure how to generalize that 0 to be any index.

Upvotes: 8

Views: 4104

Answers (4)

PavelDev
PavelDev

Reputation: 673

If we are talking about the tuple-like concept exposition, which is available since c++23, then the latest revision P2165R4 and the gcc patch give us the following implementation:

template<class T>
inline constexpr bool is_tuple_like_v = false;

template<class... Elems>
inline constexpr bool is_tuple_like_v<std::tuple<Elems...>> = true;

template<class T1, class T2>
inline constexpr bool is_tuple_like_v<std::pair<T1, T2>> = true;

template<class T, size_t N>
inline constexpr bool is_tuple_like_v<std::array<T, N>> = true;

template<class It, class Sent, std::ranges::subrange_kind Kind>
inline constexpr bool is_tuple_like_v<std::ranges::subrange<It, Sent, Kind>> = true;

template<class T>
concept tuple_like = is_tuple_like_v<std::remove_cvref_t<T>>;

template <typename T>
concept pair_like = tuple_like<T> && std::tuple_size_v<std::remove_cvref_t<T>> == 2;

Upvotes: 0

HolyBlackCat
HolyBlackCat

Reputation: 96166

Most of the time I just do this:

template<typename T>
concept TupleLike = requires{std::tuple_size<T>::value;};
// Can't use `std::tuple_size_v` as it's not SFINAE-friendly.

If something else is missing, you'll get a hard error, yes. But I don't see it as a problem, since arguably a type with an incomplete implementation of the tuple protocol is broken anyway.

One thing to note is that you must use using std::get; get<i>(t) instead of std::get<i>(t) to support user-defined types that have get() in their namespace rather than in std::. This is what C++17 structured bindings do internally.

Upvotes: 2

康桓瑋
康桓瑋

Reputation: 42766

The standard defines exposition only concept pair-like and has-tuple-element in [range.subrange] and [range.elements.view] respectively, we can extend them to the tuple-like concept

template<class T, std::size_t N>
concept has_tuple_element =
  requires(T t) {
    typename std::tuple_element_t<N, std::remove_const_t<T>>;
    { get<N>(t) } -> std::convertible_to<const std::tuple_element_t<N, T>&>;
  };

template<class T>
concept tuple_like = !std::is_reference_v<T> 
  && requires(T t) { 
    typename std::tuple_size<T>::type; 
    requires std::derived_from<
      std::tuple_size<T>, 
      std::integral_constant<std::size_t, std::tuple_size_v<T>>
    >;
  } && []<std::size_t... N>(std::index_sequence<N...>) { 
    return (has_tuple_element<T, N> && ...); 
  }(std::make_index_sequence<std::tuple_size_v<T>>());

Demo

Please be sure to check out P2165R2 which also defines a similar tuple-like concept.

Upvotes: 13

SergeyA
SergeyA

Reputation: 62563

I think, a tuple-like type is a class for which both std::tuple_size and std::get are defined.

Here is an example of how this can be conceptualized:

#include <tuple>
#include <array>

template<typename T>
concept TupleLike =
requires (T a) {
    std::tuple_size<T>::value;
    std::get<0>(a);
};


template<TupleLike T>
void foo(T t);


std::tuple<int, char> t1;
std::array<int, 10> a1;

void bar()
{
    foo(t1); // OK
    foo(a1); // OK

    foo("Hello"); //Fails
}

Please note, this will fail for empty tuples, which might be what you want, or might not.

Upvotes: 0

Related Questions