如果
TagOrInt<T>
具有成员类型 T::Tag
,我希望 T
等于 Tag
,否则等于 int
。喜欢:
template <class T> using TagOrint = typename T::Tag; // if this is valid
template <class T> using TagOrInt = int; // otherwise
我可以使用 SFINAE 和辅助结构来做到这一点:
#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>);
这可行,但是有没有办法在没有 SFINAE 和辅助结构的情况下做到这一点?我觉得应该有一种使用概念的C++20方式。或者 C++17 中有更简单的方法吗?
这是我对概念的尝试,但它不起作用:
#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>);
Tag_or_int<Y>
的实例化无法编译:
concept.cc:5:7: error: no type named ‘Tag’ in ‘struct Y’
所以显然
std::conditional_t
要求两个分支都编译。
你可以这样做:
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;
基本上 - lambda 为您提供了一个可以
if constexpr
满足该约束的位置。但是 lambda 不能返回 type,它只能返回一个值 - 所以我们返回某种 std::type_identity<T>
。然后我们需要 decltype(...)::type
将 T
实际拉出。