For use in other templates, I want to have a function like std::get for a tuple which returns an instance of a default type when the get call is invalid; the kind of
template<size_t I, typename T> auto get_or(T const& t)
{
// Pseudocode
return ("if std::get<I>(t) is valid") ? std::get<I>(t) : std::monostate{};
}
But I failed, and I wonder whether it is due to my lack of experience with C++ concepts, or maybe a bug in the compiler (Visual Studio 2022 version 17.8.4), or maybe it simply is not possible.
This is what I tried:
I defined a type trait and a concept that tell me whether T is a tuple:
template<typename T> struct is_tuple : std::false_type {};
template<typename... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type {};
namespace test_concepts
{
template<typename T> concept is_tuple = requires { ::is_tuple<T>::value; };
}
Next, I want to answer the question "given a tuple t and a compile-time constant I, would std::get<I>(t)
be valid?
template<size_t I, typename T> constexpr bool can_get (T const&) { return false; }
template<size_t I, typename T> requires test_concepts::is_tuple<T>
constexpr bool can_get(T const& t)
{
return (I < std::tuple_size_v<std::remove_cvref_t<decltype(t)>>);
}
namespace test_concepts
{
template<size_t I, typename T> concept can_get = requires(T t) { ::can_get<I>(t); };
}
// Test code
std::tuple<> t0;
std::tuple<int> t1;
std::tuple<double, float> t2;
static_assert(!can_get<0>(t0)); static_assert(!can_get<1>(t0)); static_assert(!can_get<2>(t0));
static_assert( can_get<0>(t1)); static_assert(!can_get<1>(t1)); static_assert(!can_get<2>(t1));
static_assert( can_get<0>(t2)); static_assert( can_get<1>(t2)); static_assert(!can_get<2>(t2));
This works. However, when I do the final step, I receive a compile-time error.
template<size_t I, typename T> auto get_or(T const& t)
{
return std::monostate{};
}
template<size_t I, typename T> requires test_concepts::can_get<I, T>
auto get_or(T const& t)
{
return std::get<I>(t);
}
// Test code
std::tuple<> t0;
std::tuple<int> t1;
auto x00 = get_or<0>(t0);
// I expect x00 to be of type std::monostate, but instead this error is displayed:
// error C2338: static_assert failed: 'tuple index out of bounds'
auto x10 = get_or<0>(t1);
// Context help shows that x10 is of type int as expected
auto x11 = get_or<1>(t1);
// error C2338 again for x11
Any ideas? - Thanks!
You are misusing the requires
-clause.
The requires
-clause only checks the validity of the expression without actually evaluating it, so your concept is always satisfied.
You can use nested requires
to evaluate the expression, e.g.
template<typename T> concept is_tuple = requires { requires ::is_tuple<T>::value; };
Or even simpler
template<typename T> concept is_tuple = ::is_tuple<T>::value;
The same goes for test_concepts::can_get
.