我想我可以测试(用C++14(,如果一个类包含一个类型,我可以这样做:
#include <type_traits>
struct X {
using some_type = int;
};
struct Y {};
template <typename T, typename = void>
struct has_some_type : std::false_type {};
template <typename C>
struct has_some_type<C, typename C::some_type> : std::true_type {};
static_assert(has_some_type<X>::value); // unexpectedly fails
static_assert(has_some_type<Y>::value); // correctly fails
但是static_assert
失败了,这让我很惊讶,因为检查成员函数也会以类似的方式工作。
#include <type_traits>
struct X {
void some_function();
};
struct Y {};
template <typename T, typename = void>
struct has_some_function : std::false_type {};
template <typename C>
struct has_some_function<C, decltype(std::declval<C>().some_function())> : std::true_type {};
static_assert(has_some_function<X>::value); // correctly succeeds
static_assert(has_some_function<Y>::value); // correctly fails
为什么这不起作用?我该如何测试类是否有类型?
我知道这个问题被标记为C++14,但为了其他人登陆这里:
在C++20及更高版本中,概念提供了一种干净的语法(更不用说在许多场景中还消除了对static_assert
和enable_if
的需要(,用于检查类型是否具有给定的类型成员:
template<typename T>
concept has_some_type = requires {
typename T::some_type;
};
struct X {
using some_type = int;
};
struct Y {};
static_assert(has_some_type<X>);
static_assert(!has_some_type<Y>);
为什么has_some_type
特性失败
这是因为第二个模板参数的推导只使用了主模板,而没有";接受暗示";从您的模板专业化。
当你用X
代替C
:时,想想你对has_some_type
的部分专业化
template <>
struct has_some_type<X, int> : std::true_type {};
这个替换没有失败,但是-为什么它会匹配has_some_type<X>
?当编译器看到has_some_type<X>
时,它会无情地忽略你试图引起它注意的尝试,只需使用= void
来推导第二个模板参数。
为什么has_some_function
特质成功
看看您在专业化中使用的第二种类型:decltype(std::declval<C>().some_function())
。对于X
,该类型解析为什么。。。是的,它解析为void
。所以当你用X
代替C
时,它是:
template <>
struct has_some_function<X, void> : std::true_type {};
并且该将使用来自主模板的= void
来匹配has_some_function<X>
。
我们如何修复has_some_type
特性
虽然C++17和C++20提供了更简单的解决方案(正如@Frank和@Quimby所建议的(,但让我们坚持使用C++14,并考虑修复我们已经编写的内容
从has_some_function
的例子中,我们可能会受到启发;"修复";用CCD_ 21替换主模板的类型特征;但是,虽然这适用于X
和Y
,但如果您有using some_type = double
或任何其他非int类型,它就不起作用。这不是我们想要的:-(
那么,我们可以有一个类型,它的定义是:;检查some_type
的有效性,无论它是什么,但最终只是表现为一个单一的固定类型"。。。是的,事实证明我们可以。
#include <type_traits>
struct X {
using some_type = int;
};
struct Y {};
template<typename T>
using void_t = void;
// Please Mr. Compiler, make sure T is a legit type, then use void.
template<typename, typename = void>
struct has_some_type : std::false_type { };
template<typename T>
struct has_some_type<T, void_t<typename T::some_type>> : std::true_type { };
static_assert(has_some_type<X>::value, "Unfortunately, X doesn't have some_type");
static_assert(has_some_type<Y>::value, "Unfortunately, Y doesn't have some_type");
只有第二个断言失败。
请参阅GodBolt上的操作。
备注:
- 这个解决方案实际上是有效的C++11
- C++17引入了
std::void_t
,以节省您的一些输入。它还支持可变参数包,而不仅仅是单个类型 - 如果使用旧的编译器,上面的代码可能会由于C++标准缺陷CWG1558而失败
您错误地使用了void_t
习语。正确的方法是:
template <typename C>
struct has_some_type<C, std::void_t<typename C::some_type>> : std::true_type {};
static_assert(has_some_type<X>::value);
static_assert(!has_some_type<Y>::value);
对于C++14,将void_t
定义为:
template<typename T> struct void_impl { using type=void;};
template<typename T> using void_t = typename void_impl<T>::type;
重述成语:
has_some_type<X>
使用从基本模板到has_some_type<X,void>
的默认参数完成- 编译器试图为
has_some_type<X,void>
找到所有匹配的专业化 - 考虑原始-
has_some_type<C, typename C::some_type>
,C
可以推断为X
,X::some_type
是有效的,但不是类型void
,因此专用化与使用主模板不匹配 - 固定-考虑
has_some_type<C, std::void_t<typename C::some_type>>
,可以再次推导C,这一次std::void_t<typename C::some_type>
是void
类型的有效表达式。这与主模板相匹配,并且被认为比主模板更专业,因此被选中
- 考虑原始-
这意味着,您总是希望参数中的表达式求值为默认类型。该表达式巧妙地包含了要测试的语法。
第二个例子:
struct has_some_function<C, std::void_t<decltype(std::declval<C>().some_function())>>
在这种情况下,铸造为空也会起作用:
<C, decltype((void)std::declval<C>().some_function())>
您可以使用type_traits:
#include <experimental/type_traits>
struct X { using some_type = int; };
struct Y {};
template <typename T>
using has_some_type_detector = T::some_type;
static_assert( std::experimental::is_detected_v <has_some_type_detector, X> );
static_assert( !std::experimental::is_detected_v <has_some_type_detector, Y> );