我要求一个模板技巧来检测一个类是否具有给定签名的特定成员函数。
这个问题与这里引用的http://www.gotw.ca/gotw/071.htm类似,但不一样:在 Sutter 的书中,他回答了类 C 必须提供具有特定签名的成员函数的问题,否则程序将无法编译。在我的问题中,如果一个类具有该功能,我需要做一些事情,否则做“其他事情”。
boost::serialization 也面临类似的问题,但我不喜欢他们采用的解决方案:一个模板函数默认调用具有特定签名的自由函数(您必须定义),除非您定义特定的成员函数(在他们的情况下,“序列化”采用给定类型的 2 个参数)具有特定签名,否则会发生编译错误。那就是实现侵入式和非侵入式序列化。
我不喜欢这个解决方案有两个原因:
为了非侵入性,您必须覆盖 boost::serialization 命名空间中的全局“序列化”函数,因此您可以在您的客户代码中打开命名空间提升和命名空间序列化!解决这个混乱的堆栈是 10 到 12 个函数调用。
我需要为没有该成员函数的类定义自定义行为,并且我的实体位于不同的命名空间中(并且我不想在另一个命名空间中覆盖在一个命名空间中定义的全局函数)
你能给我一个提示来解决这个难题吗?
这是一个依赖 C++11 特性的可能实现。即使它是继承的,它也能正确检测到该函数(与已接受答案中的解决方案不同,正如 Mike Kinghan 在 his answer 中观察到的)。
此代码段测试的函数称为 serialize
:
#include <type_traits>
// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.
template<typename, typename T>
struct has_serialize {
static_assert(
std::integral_constant<T, false>::value,
"Second template parameter needs to be of function type.");
};
// specialization that does the checking
template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
template<typename T>
static constexpr auto check(T*)
-> typename
std::is_same<
decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>::type; // attempt to call it and see if the return type is correct
template<typename>
static constexpr std::false_type check(...);
typedef decltype(check<C>(0)) type;
public:
static constexpr bool value = type::value;
};
用法:
struct X {
int serialize(const std::string&) { return 42; }
};
struct Y : X {};
std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1
我不确定我是否理解正确,但您可以利用 SFINAE 在编译时检测函数的存在。我的代码中的示例(测试类是否具有成员函数 size_t used_memory() const)。
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
size_t(std::vector::*p)() = &std::vector::size;
。
HasMethod<Type,method_signature>
?
编译时成员函数自省这个问题的公认答案虽然很受欢迎,但有一个障碍,可以在以下程序中观察到:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
该程序使用 GCC 4.6.3 构建,输出 110
- 通知我们 T = std::shared_ptr<int>
不 提供 int & T::operator*() const
。
如果您还不知道这个问题,那么看看标题 <memory>
中的 std::shared_ptr<T>
的定义就会明白。在该实现中,std::shared_ptr<T>
派生自它继承 operator*() const
的基类。因此,构成“查找”U = std::shared_ptr<T>
运算符的模板实例化 SFINAE<U, &U::operator*>
不会发生,因为 std::shared_ptr<T>
本身没有 operator*()
,并且模板实例化不会“继承”。
这个障碍不会影响众所周知的 SFINAE 方法,它使用“The sizeof() Trick”,仅用于检测 T
是否具有某些成员函数 mf
(参见例如 this answer 和注释)。但是确定 T::mf
存在通常(通常?)不够好:您可能还需要确定它具有所需的签名。这就是图示技术得分的地方。所需签名的指针化变体被写入模板类型的参数中,&T::mf
必须满足该参数才能使 SFINAE 探测成功。但是当 T::mf
被继承时,这种模板实例化技术给出了错误的答案。
用于 T::mf
的编译时自省的安全 SFINAE 技术必须避免在模板参数中使用 &T::mf
来实例化 SFINAE 函数模板解析所依赖的类型。相反,SFINAE 模板函数解析只能依赖于用作重载 SFINAE 探测函数的参数类型的完全相关的类型声明。
通过回答遵守此约束的问题,我将说明 E T::operator*() const
、任意 T
和 E
的编译时检测。相同的模式将应用 mutatis mutandis 来探测任何其他成员方法签名。
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
在这个解决方案中,重载的 SFINAE 探测函数 test()
被“递归调用”。 (当然它实际上根本没有被调用;它只是具有编译器解析的假设调用的返回类型。)
我们需要至少探查一个,最多两个信息点:
T::operator*() 是否存在?如果没有,我们就完成了。
鉴于 T::operator*() 存在,它的签名 ET::operator*() 是 const 吗?
我们通过评估对 test(0,0)
的单个调用的返回类型来获得答案。这是通过以下方式完成的:
typedef decltype(test<T>(0,0)) type;
此调用可能解析为 test()
的 /* SFINAE operator-exists :) */
重载,也可能解析为 /* SFINAE game over :( */
重载。它无法解析 /* SFINAE operator-has-correct-sig :) */
重载,因为它只需要一个参数,而我们传递了两个。
为什么我们要通过两个?只需强制解析排除 /* SFINAE operator-has-correct-sig :) */
。第二个论点没有其他意义。
此对 test(0,0)
的调用将解析为 /* SFINAE operator-exists :) */
,以防第一个参数 0 满足该重载的第一个参数类型,即 decltype(&A::operator*)
,与 A = T
。 0 将满足该类型,以防 T::operator*
存在。
让我们假设编译器对此说“是”。然后它使用 /* SFINAE operator-exists :) */
,它需要确定函数调用的返回类型,在这种情况下是 decltype(test(&A::operator*))
- 对 test()
的另一个调用的返回类型。
这一次,我们只传递一个参数 &A::operator*
,我们现在知道它存在,否则我们就不会在这里。对 test(&A::operator*)
的调用可能解析为 /* SFINAE operator-has-correct-sig :) */
或再次解析为 /* SFINAE game over :( */
。调用将匹配 /* SFINAE operator-has-correct-sig :) */
,以防 &A::operator*
与 A = T
一起满足该重载的单个参数类型,即 E (A::*)() const
。
如果 T::operator*
具有所需的签名,编译器将在此处说是,然后必须再次评估重载的返回类型。现在不再有“递归”:它是 std::true_type
。
如果编译器没有为调用 test(0,0)
选择 /* SFINAE operator-exists :) */
或没有为调用 test(&A::operator*)
选择 /* SFINAE operator-has-correct-sig :) */
,那么无论哪种情况,它都与 /* SFINAE game over :( */
一起使用,最终返回类型为 std::false_type
。
这是一个测试程序,显示了在不同的案例样本中产生预期答案的模板(再次是 GCC 4.6.3)。
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
这个想法有新的缺陷吗?它可以变得更通用而不会再次遇到它避免的障碍吗?
以下是一些用法片段: *所有这些的胆量都在更远的地方
检查给定类中的成员 x
。可以是 var、func、class、union 或 enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
检查成员函数 void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
检查成员变量 x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
检查成员类 x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
检查会员工会x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
检查成员枚举 x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
无论签名如何,检查任何成员函数 x
:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
或者
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
细节和核心:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
宏(暗黑破坏神!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
如果您知道您期望的成员函数的名称,这就足够了。 (在这种情况下,如果没有成员函数,函数 bla 将无法实例化(编写一个无论如何都可以工作的函数很困难,因为缺少函数部分特化。您可能需要使用类模板)此外,启用结构(类似于 enable_if) 也可以根据您希望它作为成员的函数类型进行模板化。
template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}
使用 c++ 20,这变得更加简单。假设我们要测试类 T
是否具有成员函数 void T::resize(typename T::size_type)
。例如,std::vector<U>
就有这样一个成员函数。然后,
template<typename T>
concept has_resize_member_func = requires {
typename T::size_type;
{ std::declval<T>().resize(std::declval<typename T::size_type>()) } -> std::same_as<void>;
};
用法是
static_assert(has_resize_member_func<std::string>, "");
static_assert(has_resize_member_func<int> == false, "");
这是对 Mike Kinghan 的回答的一个更简单的看法。这将检测继承的方法。它还将检查确切的签名(与 jrok 允许参数转换的方法不同)。
template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);
template <class T>
static decltype(testSignature(&T::greet)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");
可运行example
using
从基类带来重载。它适用于 MSVC 2015 和 Clang-CL。但是,它不适用于 MSVC 2012。
您似乎想要检测器成语。以上答案是适用于 C++11 或 C++14 的变体。
std::experimental
库具有基本上可以做到这一点的功能。从上面修改一个例子,它可能是:
#include <experimental/type_traits>
// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));
// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;
如果您不能使用 std::experimental,可以像这样制作一个基本版本:
template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};
// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>().serialize(std::declval<int>()));
// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;
由于 has_serialize_t 实际上是 std::true_type 或 std::false_type,它可以通过任何常见的 SFINAE 习语使用:
template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}
或者通过使用具有重载分辨率的调度:
template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
// call serialize here.
}
template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
// do something else here.
}
template<class T>
std::string Serialize(const T& t) {
return SerializeImpl(has_serialize_t<T>{}, t);
}
我自己也遇到了同样的问题,发现这里提出的解决方案非常有趣......但需要一个解决方案:
也检测继承的函数;与非 C++11 就绪的编译器兼容(所以没有 decltype)
发现另一个 thread 基于 BOOST discussion 提出了类似的建议。以下是将提议的解决方案概括为特征类的两个宏声明,遵循 boost::has_* 类的模型。
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>
/// Has constant function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)
/// Has non-const function
/** \param func_ret_type Function return type
\param func_name Function name
\param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
__DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)
// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...) \
template \
< typename Type, \
bool is_class = boost::is_class<Type>::value \
> \
class has_func_ ## func_name; \
template<typename Type> \
class has_func_ ## func_name<Type,false> \
{public: \
BOOST_STATIC_CONSTANT( bool, value = false ); \
typedef boost::false_type type; \
}; \
template<typename Type> \
class has_func_ ## func_name<Type,true> \
{ struct yes { char _foo; }; \
struct no { yes _foo[2]; }; \
struct Fallback \
{ func_ret_type func_name( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const) {} \
}; \
struct Derived : public Type, public Fallback {}; \
template <typename T, T t> class Helper{}; \
template <typename U> \
static no deduce(U*, Helper \
< func_ret_type (Fallback::*)( __VA_ARGS__ ) \
UTILITY_OPTIONAL(func_const,const), \
&U::func_name \
>* = 0 \
); \
static yes deduce(...); \
public: \
BOOST_STATIC_CONSTANT( \
bool, \
value = sizeof(yes) \
== sizeof( deduce( static_cast<Derived*>(0) ) ) \
); \
typedef ::boost::integral_constant<bool,value> type; \
BOOST_STATIC_CONSTANT(bool, is_const = func_const); \
typedef func_ret_type return_type; \
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type; \
}
// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__
这些宏扩展为具有以下原型的特征类:
template<class T>
class has_func_[func_name]
{
public:
/// Function definition result value
/** Tells if the tested function is defined for type T or not.
*/
static const bool value = true | false;
/// Function definition result type
/** Type representing the value attribute usable in
http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
*/
typedef boost::integral_constant<bool,value> type;
/// Tested function constness indicator
/** Indicates if the tested function is const or not.
This value is not deduced, it is forced depending
on the user call to one of the traits generators.
*/
static const bool is_const = true | false;
/// Tested function return type
/** Indicates the return type of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef func_ret_type return_type;
/// Tested function arguments types
/** Indicates the arguments types of the tested function.
This value is not deduced, it is forced depending
on the user's arguments to the traits generators.
*/
typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};
那么,一个典型的用途是什么?
// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
// Next line will declare the traits class
// to detect the member function void foo(int,int) const
DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}
// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>
// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ _this_.foo(a,b);
}
// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{ default_foo(_this_,a,b);
}
// Let us declare test types
struct empty
{
};
struct direct_foo
{
void foo(int,int);
};
struct direct_const_foo
{
void foo(int,int) const;
};
struct inherited_const_foo :
public direct_const_foo
{
};
// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
int a;
foo_bar(a); // calls default_foo
empty b;
foo_bar(b); // calls default_foo
direct_foo c;
foo_bar(c); // calls default_foo (member function is not const)
direct_const_foo d;
foo_bar(d); // calls d.foo (member function is const)
inherited_const_foo e;
foo_bar(e); // calls e.foo (inherited member function)
}
为此,我们需要使用:
根据方法是否可用,函数模板重载具有不同的返回类型为了与 type_traits 标头中的元条件保持一致,我们希望从重载中返回 true_type 或 false_type 声明期望 int 的 true_type 重载和 false_type 重载期望可变参数利用:“重载决议中省略号转换的最低优先级” 在为 true_type 函数定义模板规范时,我们将使用 declval 和 decltype 允许我们检测函数,而不受返回类型差异或方法之间的重载的影响
您可以查看此 here 的实际示例。但我还将在下面对其进行解释:
我想检查一个名为 test
的函数是否存在,它采用可从 int
转换的类型,然后我需要声明这两个函数:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
decltype(hasTest(0))::value 为真(注意不需要创建特殊功能来处理 void a::test() 重载,接受 void a::test(int))
decltype(hasTest(0))::value 为真(因为 int 可转换为 double int b::test(double) 被接受,与返回类型无关)
decltype(hasTest
该解决方案有两个缺点:
需要一对函数的每个方法声明创建命名空间污染,特别是如果我们想要测试相似的名称,例如,我们会为想要测试 test() 方法的函数命名什么?
因此,在详细信息命名空间中声明这些函数很重要,或者理想情况下,如果它们仅与类一起使用,则应由该类私有声明。为此,我编写了一个宏来帮助您抽象这些信息:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
你可以这样使用:
namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
随后调用 details::test_int<a>::value
或 details::test_void<a>::value
将产生 true
或 false
用于内联代码或元编程。
您可以使用 std::is_member_function_pointer
class A {
public:
void foo() {};
}
bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;
A
中根本没有 foo
,&A::foo
会不会是编译错误?我认为原始问题应该适用于任何输入类,而不仅仅是具有某种名为 foo
的成员的类。
A
的成员,则会给出编译错误。
为了不打扰,您还可以将 serialize
放在正在序列化的类或存档类的命名空间中,这要归功于 Koenig lookup。有关详细信息,请参阅 Namespaces for Free Function Overrides。 :-)
打开任何给定的命名空间来实现自由功能是完全错误的。 (例如,您不应该打开命名空间 std
来为您自己的类型实现 swap
,而应该使用 Koenig 查找。)
好的。第二次尝试。如果你不喜欢这个也没关系,我正在寻找更多的想法。
Herb Sutter 的文章谈到了特质。因此,您可以拥有一个特征类,其默认实例化具有回退行为,并且对于您的成员函数存在的每个类,特征类都专门用于调用成员函数。我相信 Herb 的文章提到了一种技术来做到这一点,因此它不涉及大量的复制和粘贴。
不过,就像我说的那样,也许您不希望与实现该成员的“标记”类相关的额外工作。在这种情况下,我正在寻找第三种解决方案......
我有类似的需求并遇到了这个 SO。这里提出了许多有趣/强大的解决方案,尽管对于特定需求来说有点长:检测类是否具有具有精确签名的成员函数。所以我做了一些阅读/测试,并提出了我可能感兴趣的版本。它检测:
静态成员函数
非静态成员函数
非静态成员函数 const
带有精确的签名。因为我不需要捕获任何签名(这需要更复杂的解决方案),所以这个适合我。它基本上使用了 enable_if_t。
struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };
// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T>
> : std::true_type {};
template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
> : std::true_type {};
template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <T,
std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
> : std::true_type {};
int main ()
{
constexpr bool has_sum_val = has_static_sum<Foo>::value;
constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;
constexpr bool has_calc_val = has_calc<Bar>::value;
constexpr bool not_has_calc_val = !has_calc<Foo>::value;
constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;
std::cout<< " has_sum_val " << has_sum_val << std::endl
<< " not_has_sum_val " << not_has_sum_val << std::endl
<< " has_calc_val " << has_calc_val << std::endl
<< " not_has_calc_val " << not_has_calc_val << std::endl
<< " has_calc_const_val " << has_calc_const_val << std::endl
<< "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}
输出 :
has_sum_val 1
not_has_sum_val 1
has_calc_val 1
not_has_calc_val 1
has_calc_const_val 1
not_has_calc_const_val 1
如果没有 C++11 支持 (decltype
),这可能会起作用:
SSCCE
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
希望如何运作
A
、Aa
和 B
是有问题的类,Aa
是继承我们正在寻找的成员的特殊类。
在 FooFinder
中,true_type
和 false_type
是对应的 C++11 类的替代品。同样为了理解模板元编程,它们揭示了 SFINAE-sizeof-trick 的基础。
TypeSink
是一个模板结构,稍后用于将 sizeof
运算符的积分结果放入模板实例化以形成类型。
match
函数是另一种 SFINAE 类型的模板,它没有通用的对应物。因此,只有当其参数的类型与它专门用于的类型匹配时,它才能被实例化。
test
函数和枚举声明最终形成了中心 SFINAE 模式。有一个通用的使用省略号返回 false_type
和具有更具体参数的对应项优先。
为了能够使用模板参数 T
实例化 test
函数,必须实例化 match
函数,因为实例化 TypeSink
参数需要其返回类型。需要注意的是,&U::foo
被包装在函数参数中,不在模板参数特化中被引用,因此继承的成员查找仍然发生。
如果您使用的是 facebook 愚蠢,它们是开箱即用的宏来帮助您:
#include <folly/Traits.h>
namespace {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace
void some_func() {
cout << "Does class Foo have a member int test() const? "
<< boolalpha << has_test_traits<Foo, int() const>::value;
}
虽然实现细节与前面的答案相同,但使用库更简单。
在 jrok 的 answer 的基础上,我避免使用嵌套模板类和/或函数。
#include <type_traits>
#define CHECK_NESTED_FUNC(fName) \
template <typename, typename, typename = std::void_t<>> \
struct _has_##fName \
: public std::false_type {}; \
\
template <typename Class, typename Ret, typename... Args> \
struct _has_##fName<Class, Ret(Args...), \
std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
: public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
{}; \
\
template <typename Class, typename Signature> \
using has_##fName = _has_##fName<Class, Signature>;
#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value
我们可以使用上面的宏,如下所示:
class Foo
{
public:
void Bar(int, const char *) {}
};
CHECK_NESTED_FUNC(Bar); // generate required metafunctions
int main()
{
using namespace std;
cout << boolalpha
<< HAS_NESTED_FUNC(Foo, Bar, void(int, const char *)) // prints true
<< endl;
return 0;
}
欢迎提出建议。
不定期副业成功案例分享
serialize
本身接受模板会怎样。有没有办法在不输入确切类型的情况下测试serialize
的存在?