如何测试一个类型是否在另一个类中



我想我可以测试(用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_assertenable_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替换主模板的类型特征;但是,虽然这适用于XY,但如果您有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;

重述成语:

  1. has_some_type<X>使用从基本模板到has_some_type<X,void>的默认参数完成
  2. 编译器试图为has_some_type<X,void>找到所有匹配的专业化
    • 考虑原始-has_some_type<C, typename C::some_type>C可以推断为XX::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> );

最新更新