Reputation: 5523
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
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
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>>());
Please be sure to check out P2165R2 which also defines a similar tuple-like
concept.
Upvotes: 13
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