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

How do I test if a type is within another class exists?

我在想我可以测试(使用 C++14)如果 class 包含一个类型,我可以这样做:

#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

为什么这不起作用,我如何测试 class 是否有类型?

您使用的 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. 的默认参数完成
  3. 编译器尝试为 has_some_type<X,void>.
  4. 找到所有匹配的特化
    • 原文 - has_some_type<C, typename C::some_type> 被认为,C可以推导为XX::some_type有效但不是 void 类型,因此专业化与使用的主模板不匹配。
    • 考虑
    • Fixed - 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())>> 

转换为 void 也适用于这种情况:

<C, decltype((void)std::declval<C>().some_function())>

我知道这个问题被标记为 C++14,但为了其他人登陆这里:

在 C++20 及更高版本中,concepts 提供了一种简洁的语法(更不用说在许多情况下也消除了对 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 {};

并且这个 匹配 has_some_function<X>,使用主模板中的 = void

我们如何修复 has_some_type 特征?

虽然 C++17 和 C++20 提供了更简单的解决方案(正如@Frank 和@Quimby 所建议的)——让我们坚持使用 C++14 并考虑修复我们已经存在的问题写了。

has_some_function 示例中,我们可能会受到启发,通过将主模板替换为 template <typename T, typename = int> 来“修复”类型特征;但是虽然这对 XY 有效,但如果你有 using some_type = double 或任何其他非整数类型,它就不会起作用。这不是我们想要的:-(

那么,我们能否拥有一个类型,其定义是:“检查 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++ standard defect CWG1558而失败。

您可以使用 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> );