I want TagOrInt<T>
to be equal to T::Tag
if T
has a member type Tag
, and equal to int
otherwise. Like:
template <class T> using TagOrint = typename T::Tag; // if this is valid
template <class T> using TagOrInt = int; // otherwise
I can do this using SFINAE and a helper struct:
#include <type_traits>
template<class T, class = void>
struct TagOrIntHelper {
using type = int;
};
template<class T>
struct TagOrIntHelper<T, std::void_t<typename T::Tag> > {
using type = T::Tag;
};
template<class T>
using TagOrInt = TagOrIntHelper<T>::type;
// Test case
struct X { using Tag = float; };
struct Y { };
static_assert(std::is_same_v<TagOrInt<X>, float>);
static_assert(std::is_same_v<TagOrInt<Y>, int>);
This works, but is there a way to do it without SFINAE and a helper struct? I feel that there should be a C++20 way that uses concepts. Or is there an even a simpler way in C++17?
This was my attempt with concepts, but it does not work:
#include <type_traits>
template<class T>
concept HasTag = requires { (typename T::Tag *)nullptr; };
template<class T>
using TagOrInt = std::conditional_t<HasTag<T>, typename T::Tag, int>;
// Test case
struct Y { };
static_assert(std::is_same_v<TagOrInt<Y>, int>);
The instantiation of Tag_or_int<Y>
fails to compile:
concept.cc:5:7: error: no type named ‘Tag’ in ‘struct Y’
So apparently std::conditional_t
requires that both branches compile.
(It was suggested that this question is a duplicate of How to deduce the type of template argument based on some conditions and return information about that type . However, the two questions are unrelated:
You could do it this way:
template <class T>
using TagOrint = decltype([]{
if constexpr (requires { typename T::Tag; }) {
return std::type_identity<typename T::Tag>();
} else {
return std::type_identity<T>();
}
}())::type;
Basically - the lambda gives you a place where you can if constexpr
on that constraint. But the lambda can't return a type, it can only return a value - so we instead return some kind of std::type_identity<T>
. And then we need to decltype(...)::type
to actually pull the T
out.