C++ 检查类是否具有给定签名的成员函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/87372/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:45:15  来源:igfitidea点击:

Check if a class has a member function of a given signature

c++c++11templatessfinae

提问by ugasoft

I'm asking for a template trick to detect if a class has a specific member function of a given signature.

我要求使用模板技巧来检测类是否具有给定签名的特定成员函数。

The problem is similar to the one cited here http://www.gotw.ca/gotw/071.htmbut not the same: in the item of Sutter's book he answered to the question that a class C MUST PROVIDE a member function with a particular signature, else the program won't compile. In my problem I need to do something if a class has that function, else do "something else".

该问题与此处引用的问题类似 http://www.gotw.ca/gotw/071.htm但不一样:在 Sutter 的书中,他回答了一个问题,即类 C 必须提供成员函数一个特定的签名,否则程序将无法编译。在我的问题中,如果一个类具有该功能,我需要做一些事情,否则做“其他事情”。

A similar problem was faced by boost::serialization but I don't like the solution they adopted: a template function that invokes by default a free function (that you have to define) with a particular signature unless you define a particular member function (in their case "serialize" that takes 2 parameters of a given type) with a particular signature, else a compile error will happens. That is to implement both intrusive and non-intrusive serialization.

boost::serialization 也面临类似的问题,但我不喜欢他们采用的解决方案:模板函数默认调用具有特定签名的自由函数(您必须定义),除非您定义特定成员函数(在他们的情况下,“序列化”需要给定类型的 2 个参数)具有特定签名,否则会发生编译错误。即实现侵入式和非侵入式序列化。

I don't like that solution for two reasons:

我不喜欢这个解决方案有两个原因:

  1. To be non intrusive you must override the global "serialize" function that is in boost::serialization namespace, so you have IN YOUR CLIENT CODE to open namespace boost and namespace serialization!
  2. The stack to resolve that mess was 10 to 12 function invocations.
  1. 为了非侵入性,您必须覆盖 boost::serialization 命名空间中的全局“序列化”函数,因此您必须在您的客户端代码中打开命名空间 boost 和命名空间序列化!
  2. 解决这个混乱的堆栈是 10 到 12 个函数调用。

I need to define a custom behavior for classes that has not that member function, and my entities are inside different namespaces (and I don't want to override a global function defined in one namespace while I'm in another one)

我需要为没有该成员函数的类定义自定义行为,并且我的实体位于不同的命名空间内(并且我不想在我在另一个命名空间中时覆盖在一个命名空间中定义的全局函数)

Can you give me a hint to solve this puzzle?

你能给我一个提示来解决这个难题吗?

采纳答案by yrp

I'm not sure if I understand you correctly, but you may exploit SFINAE to detect function presence at compile-time. Example from my code (tests if class has member function size_t used_memory() const).

我不确定我是否理解正确,但您可以利用 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>());
}

回答by jrok

Here's a possible implementation relying on C++11 features. It correctly detects the function even if it's inherited (unlike the solution in the accepted answer, as Mike Kinghan observes in his answer).

这是一个依赖 C++11 特性的可能实现。即使它是继承的,它也能正确检测到该函数(与已接受答案中的解决方案不同,正如 Mike Kinghan 在他的答案中所观察到的那样)。

The function this snippet tests for is called serialize:

此代码段测试的函数称为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;
};

Usage:

用法:

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

回答by Mike Kinghan

The accepted answer to this question of compiletime member-function introspection, although it is justly popular, has a snag which can be observed in the following program:

对编译时成员函数自省这个问题的公认答案,虽然它很受欢迎,但有一个障碍,可以在以下程序中观察到:

#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;
}

Built with GCC 4.6.3, the program outputs 110- informing us that T = std::shared_ptr<int>does notprovide int & T::operator*() const.

建有GCC 4.6.3,方案产出110-并告诉我们 T = std::shared_ptr<int>不能提供int & T::operator*() const

If you are not already wise to this gotcha, then a look at of the definition of std::shared_ptr<T>in the header <memory>will shed light. In that implementation, std::shared_ptr<T>is derived from a base class from which it inherits operator*() const. So the template instantiation SFINAE<U, &U::operator*>that constitutes "finding" the operator for U = std::shared_ptr<T>will not happen, because std::shared_ptr<T>has no operator*()in its own right and template instantiation does not "do inheritance".

如果您对这个问题还不太了解,那么查看std::shared_ptr<T>标题中的定义 <memory>就会明白。在该实现中,std::shared_ptr<T>从它继承的基类派生operator*() const。所以SFINAE<U, &U::operator*>构成“寻找”运算符 的模板实例化 U = std::shared_ptr<T>不会发生,因为它本身std::shared_ptr<T>没有 operator*(),模板实例化不“继承”。

This snag does not affect the well-known SFINAE approach, using "The sizeof() Trick", for detecting merely whether Thas some member function mf(see e.g. this answerand comments). But establishing that T::mfexists is often (usually?) not good enough: you may also need to establish that it has a desired signature. That is where the illustrated technique scores. The pointerized variant of the desired signature is inscribed in a parameter of a template type that must be satisfied by &T::mffor the SFINAE probe to succeed. But this template instantiating technique gives the wrong answer when T::mfis inherited.

这个障碍不会影响众所周知的 SFINAE 方法,它使用“sizeof() 技巧”,仅用于检测是否T具有某些成员函数mf(参见例如 此答案和评论)。但是确定T::mf存在通常(通常?)不够好:您可能还需要确定它具有所需的签名。这就是插图技术得分的地方。所需签名的指针化变体被写入模板类型的参数中&T::mf,SFINAE 探测必须满足该参数 才能成功。但是这种模板实例化技术在T::mf继承时给出了错误的答案。

A safe SFINAE technique for compiletime introspection of T::mfmust avoid the use of &T::mfwithin a template argument to instantiate a type upon which SFINAE function template resolution depends. Instead, SFINAE template function resolution can depend only upon exactly pertinent type declarations used as argument types of the overloaded SFINAE probe function.

用于编译时自省的安全 SFINAE 技术T::mf必须避免&T::mf在模板参数中使用来实例化 SFINAE 函数模板解析所依赖的类型。相反,SFINAE 模板函数解析只能依赖于用作重载 SFINAE 探测函数的参数类型的完全相关的类型声明。

By way of an answer to the question that abides by this constraint I'll illustrate for compiletime detection of E T::operator*() const, for arbitrary Tand E. The same pattern will apply mutatis mutandisto probe for any other member method signature.

作为对遵守此约束的问题的回答,我将说明E T::operator*() const、任意T和 的编译时检测E。相同的模式将比照适用于探测任何其他成员方法签名。

#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? */
};

In this solution, the overloaded SFINAE probe function test()is "invoked recursively". (Of course it isn't actually invoked at all; it merely has the return types of hypothetical invocations resolved by the compiler.)

在这个解决方案中,重载的 SFINAE 探测函数test()是“递归调用的”。(当然,它实际上根本没有被调用;它只有编译器解析的假设调用的返回类型。)

We need to probe for at least one and at most two points of information:

我们需要探查至少一个,最多两个信息点:

  • Does T::operator*()exist at all? If not, we're done.
  • Given that T::operator*()exists, is its signature E T::operator*() const?
  • 是否T::operator*()都存在吗?如果没有,我们就完成了。
  • 鉴于T::operator*()存在,它的签名E T::operator*() const是否存在 ?

We get the answers by evaluating the return type of a single call to test(0,0). That's done by:

我们通过评估单个调用的返回类型来获得答案test(0,0)。这是由:

    typedef decltype(test<T>(0,0)) type;

This call might be resolved to the /* SFINAE operator-exists :) */overload of test(), or it might resolve to the /* SFINAE game over :( */overload. It can't resolve to the /* SFINAE operator-has-correct-sig :) */overload, because that one expects just one argument and we are passing two.

此调用可能会解析为 的/* SFINAE operator-exists :) */重载test(),也可能会解析为/* SFINAE game over :( */重载。它无法解析为/* SFINAE operator-has-correct-sig :) */重载,因为它只需要一个参数,而我们正在传递两个参数。

Why are we passing two? Simply to force the resolution to exclude /* SFINAE operator-has-correct-sig :) */. The second argument has no other signifance.

为什么我们要通过两个?简单地强制分辨率排除 /* SFINAE operator-has-correct-sig :) */. 第二个论点没有其他意义。

This call to test(0,0)will resolve to /* SFINAE operator-exists :) */just in case the first argument 0 satifies the first parameter type of that overload, which is decltype(&A::operator*), with A = T. 0 will satisfy that type just in case T::operator*exists.

这个调用test(0,0)将解析为/* SFINAE operator-exists :) */以防第一个参数 0 满足该重载的第一个参数类型,即decltype(&A::operator*), with A = T。0 将满足该类型,以防万一T::operator*

Let's suppose the compiler say's Yes to that. Then it's going with /* SFINAE operator-exists :) */and it needs to determine the return type of the function call, which in that case is decltype(test(&A::operator*))- the return type of yet another call to test().

让我们假设编译器对此说“是”。然后它继续, /* SFINAE operator-exists :) */它需要确定函数调用的返回类型,在这种情况下是decltype(test(&A::operator*))- 另一个调用的返回类型test()

This time, we're passing just one argument, &A::operator*, which we now know exists, or we wouldn't be here. A call to test(&A::operator*)might resolve either to /* SFINAE operator-has-correct-sig :) */or again to might resolve to /* SFINAE game over :( */. The call will match /* SFINAE operator-has-correct-sig :) */just in case &A::operator*satisfies the single parameter type of that overload, which is E (A::*)() const, with A = T.

这一次,我们只传递一个参数,&A::operator*,我们现在知道它存在,否则我们就不会在这里。调用 totest(&A::operator*)可能解析为/* SFINAE operator-has-correct-sig :) */或再次解析为/* SFINAE game over :( */。调用将匹配 /* SFINAE operator-has-correct-sig :) */,以防万一&A::operator*满足该重载的单个参数类型,即E (A::*)() const, with A = T

The compiler will say Yes here if T::operator*has that desired signature, and then again has to evaluate the return type of the overload. No more "recursions" now: it is std::true_type.

如果T::operator*具有所需的签名,编译器将在此处说 Yes ,然后再次评估重载的返回类型。现在没有更多的“递归”:它是std::true_type.

If the compiler does not choose /* SFINAE operator-exists :) */for the call test(0,0)or does not choose /* SFINAE operator-has-correct-sig :) */for the call test(&A::operator*), then in either case it goes with /* SFINAE game over :( */and the final return type is std::false_type.

如果编译器不选择/* SFINAE operator-exists :) */调用test(0,0)或不选择/* SFINAE operator-has-correct-sig :) */调用test(&A::operator*),那么在任何一种情况下它都会使用 /* SFINAE game over :( */并且最终返回类型是std::false_type.

Here is a test program that shows the template producing the expected answers in varied sample of cases (GCC 4.6.3 again).

这是一个测试程序,它显示了在不同案例样本中产生预期答案的模板(再次是 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;
}

Are there new flaws in this idea? Can it be made more generic without once again falling foul of the snag it avoids?

这个想法有新的缺陷吗?它能否变得更通用,而不会再次陷入它所避免的障碍?

回答by Brett Rossier

Here are some usage snippets: *The guts for all this are farther down

以下是一些使用片段: *所有这些的胆量都在更远的地方

Check for member xin a given class. Could be var, func, class, union, or enum:

检查x给定类中的成员。可以是 var、func、class、union 或 enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Check for member function void x():

检查成员函数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;

Check for member variable x:

检查成员变量x

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Check for member class x:

检查成员类x

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Check for member union x:

检查会员工会x

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Check for member enum x:

检查成员枚举x

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Check for any member function xregardless of signature:

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;

OR

或者

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;

Details and core:

细节和核心:

/*
    - 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;
};

Macros (El Diablo!):

宏(暗黑破坏神!):

CREATE_MEMBER_CHECK:

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:

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:

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:

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:

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:

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:

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_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)

回答by coppro

This should be sufficient, if you know the name of the member function you are expecting. (In this case, the function bla fails to instantiate if there is no member function (writing one that works anyway is tough because there is a lack of function partial specialization. You may need to use class templates) Also, the enable struct (which is similar to enable_if) could also be templated on the type of function you want it to have as a 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);
}

回答by Valentin Milea

Here is a simpler take on Mike Kinghan's answer. This will detect inherited methods. It will also check for the exactsignature (unlike jrok's approach which allows argument conversions).

这是对 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, "");

Runnable example

可运行示例

回答by Jonathan Mee

To accomplish this we'll need to use:

为了实现这一点,我们需要使用:

  1. Function template overloadingwith differing return types according to whether the method is available
  2. In keeping with the meta-conditionals in the type_traitsheader, we'll want to return a true_typeor false_typefrom our overloads
  3. Declare the true_typeoverload expecting an intand the false_typeoverload expecting Variadic Parameters to exploit: "The lowest priority of the ellipsis conversion in overload resolution"
  4. In defining the template specification for the true_typefunction we will use declvaland decltypeallowing us to detect the function independent of return type differences or overloads between methods
  1. 根据方法是否可用,具有不同返回类型的函数模板重载
  2. 为了与type_traits标题中的元条件保持一致,我们希望从我们的重载中返回 atrue_typefalse_type
  3. 声明需要使用的true_type重载int和需要使用false_type可变参数的重载:“重载解析中省略号转换的最低优先级”
  4. 在定义true_type函数的模板规范时,我们将使用declvaldecltype允许我们独立于方法之间的返回类型差异或重载来检测函数

You can see a live example of this here.But I'll also explain it below:

您可以在此处查看此示例。但我也会在下面解释:

I want to check for the existence of a function named testwhich takes a type convertible from int, then I'd need to declare these two functions:

我想检查是否存在一个名为的函数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<a>(0))::valueis true(Note there is no need to create special functionality to deal with the void a::test()overload, the void a::test(int)is accepted)
  • decltype(hasTest<b>(0))::valueis true(Because intis convertable to doubleint b::test(double)is accepted, independent of return type)
  • decltype(hasTest<c>(0))::valueis false(cdoes not have a method named testthat accepts a type convertible from inttherefor this is not accepted)
  • decltype(hasTest<a>(0))::valueis true(注意不需要创建特殊功能来处理void a::test()重载,void a::test(int)接受)
  • decltype(hasTest<b>(0))::valuetrue(因为int可以转换为doubleint b::test(double)被接受,与返回类型无关)
  • decltype(hasTest<c>(0))::valuefalsec没有一个名为的方法test可以接受可从中转换的类型,int因此不接受)

This solution has 2 drawbacks:

该解决方案有两个缺点:

  1. Requires a per method declaration of a pair of functions
  2. Creates namespace pollution particularly if we want to test for similar names, for example what would we name a function that wanted to test for a test()method?
  1. 需要一对函数的每个方法声明
  2. 尤其是如果我们想测试相似的名称时,会造成命名空间污染,例如,我们会为想要测试test()方法的函数命名什么?

So it's important that these functions be declared in a details namespace, or ideally if they are only to be used with a class, they should be declared privately by that class. To that end I've written a macro to help you abstract this information:

因此,在 details 命名空间中声明这些函数很重要,或者理想情况下,如果它们仅用于某个类,则应由该类私下声明它们。为此,我编写了一个宏来帮助您抽象这些信息:

#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));

You could use this like:

你可以这样使用:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Subsequently calling details::test_int<a>::valueor details::test_void<a>::valuewould yield trueor falsefor the purposes of inline code or meta-programming.

随后调用details::test_int<a>::valueordetails::test_void<a>::value将 yield trueorfalse用于内联代码或元编程。

回答by Yochai Timmer

You can use std::is_member_function_pointer

您可以使用std::is_member_function_pointer

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

回答by S. Paris

Came with the same kind of problem myself, and found the proposed solutions in here very interesting... but had the requirement for a solution that:

我自己也遇到了同样的问题,并发现这里提出的解决方案非常有趣……但是需要一个解决方案:

  1. Detects inherited functions as well;
  2. Is compatible with non C++11 ready compilers (so no decltype)
  1. 也检测继承的函数;
  2. 与非 C++11 的编译器兼容(所以没有 decltype)

Found another threadproposing something like this, based on a BOOST discussion. Here is the generalisation of the proposed solution as two macros declaration for traits class, following the model of boost::has_*classes.

根据BOOST 讨论,找到了另一个提出类似建议的线程。以下是根据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__

These macros expand to a traits class with the following prototype:

这些宏扩展为具有以下原型的特征类:

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;
};

So what is the typical usage one can do out of this?

那么,人们可以从中做什么的典型用法是什么?

// 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)
}

回答by Chris Jester-Young

To be non-intrusive, you can also put serializein the namespace of the class being serialised, or of the archive class, thanks to Koenig lookup. See Namespaces for Free Function Overridesfor more details. :-)

为了非侵入性,您还可以放入serialize正在序列化的类或存档类的命名空间中,这要归功于Koenig 查找。有关更多详细信息,请参阅用于自由函数覆盖的命名空间。:-)

Opening up any given namespace to implement a free function is Simply Wrong. (e.g., you're not supposed to open up namespace stdto implement swapfor your own types, but should use Koenig lookup instead.)

开放任何给定的命名空间来实现自由功能是完全错误的。(例如,您不应该打开命名空间stdswap为您自己的类型实现,而应该使用 Koenig 查找。)