I have an indexable type trait, that can be specialized:
// Traits
template <size_t Idx, typename T>
struct nth_type;
template <size_t Idx, typename T>
using nth_type_t = typename nth_type<Idx, T>::type;
template <typename T>
struct num_types;
template <typename T>
inline constexpr size_t num_types_v = num_types<T>::value;
// Specialization
template <typename... Ts>
struct type_sequence {};
template <size_t Idx, typename... Ts>
struct nth_type<Idx, type_sequence<Ts...>>
: std::type_identity<std::tuple_element_t<Idx, std::tuple<Ts...>>
{};
template <typename... Ts>
struct num_types<type_sequence<Ts...>>
{
static constexpr size_t value = sizeof...(Ts);
};
How can I write a concept that requires that typename nth_type<Idx, T> is valid and names a type for every value in [0, num_types_v<T>)
i.e.:
template<typename T>
concept has_types = requires {
typename nth_type<0, T>;
typename nth_type<1, T>;
// ...
typename nth_type<num_types<T> - 1, T>;
};
>Solution :
It always helps to break the problem down into pieces.
First, write the concept for whether the Ith type is defined:
template <size_t I, typename T>
concept has_nth_type = requires {
typename nth_type<I, T>;
};
Next, we can write a helper function to explode an index. That is, indices<4>(f) calls f(0, 1, 2, 3) except instead of 0 it’s integral_constant<size_t, 0>{}, which preserves the constant-ness of the value better. This is just a generally-useful utility:
template <size_t N, class F>
constexpr auto indices(F&& f) -> decltype(auto) {
return [&]<size_t... Is>(std::index_sequence<Is...>) -> decltype(auto) {
return f(std::integral_constant<size_t, Is>{}...);
}(std::make_index_sequence<N>{});
}
Lastly, we combine those two together:
template<typename T>
concept has_types =
indices<num_types_v<T>>([](auto... Is){
return (has_nth_type<Is, T> and ...);
});