如何测试一个类型是否在另一个 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;
重述成语:
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
类型,因此专业化与使用的主模板不匹配。
考虑- 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_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 {};
并且这个 将 匹配 has_some_function<X>
,使用主模板中的 = void
。
我们如何修复 has_some_type
特征?
虽然 C++17 和 C++20 提供了更简单的解决方案(正如@Frank 和@Quimby 所建议的)——让我们坚持使用 C++14 并考虑修复我们已经存在的问题写了。
从 has_some_function
示例中,我们可能会受到启发,通过将主模板替换为 template <typename T, typename = int>
来“修复”类型特征;但是虽然这对 X
和 Y
有效,但如果你有 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> );
我在想我可以测试(使用 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;
重述成语:
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
类型,因此专业化与使用的主模板不匹配。
考虑 - 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_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 {};
并且这个 将 匹配 has_some_function<X>
,使用主模板中的 = void
。
我们如何修复 has_some_type
特征?
虽然 C++17 和 C++20 提供了更简单的解决方案(正如@Frank 和@Quimby 所建议的)——让我们坚持使用 C++14 并考虑修复我们已经存在的问题写了。
从 has_some_function
示例中,我们可能会受到启发,通过将主模板替换为 template <typename T, typename = int>
来“修复”类型特征;但是虽然这对 X
和 Y
有效,但如果你有 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> );