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


135

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

这个问题类似于一个引用在这里 http://www.gotw.ca/gotw/071.htm ,但不一样的:在萨特的书的项目,他回答的问题是C类必须提供带成员函数特定签名,否则程序将无法编译。在我的问题中,如果一个类具有该功能,则需要执行某些操作,否则执行“其他操作”。

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

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

  1. 要成为非侵入式,您必须覆盖boost :: serialization名称空间中的全局“序列化”功能,以便您可以在客户端代码中打开名称空间boost和名称空间序列化!
  2. 解决该混乱的堆栈是10到12个函数调用。

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

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



@ R.MartinhoFernandes您在寻找什么样的答案?迈克·金汉(Mike Kinghan)的答案相当深入,并且使用的是C ++ 11。
jrok

@ R.MartinhoFernandes也许是您正在寻找的现代版本?
丹尼尔·弗雷

Answers:


90

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

14
wtf是这个???它是合法的C ++代码吗?您可以写“ template <typename U,size_t(U :: *)()const>”吗?但是...这是一个很棒的新解决方案!感谢您,明天我会和同事们一起分析更好的事情……太好了!
ugasoft

2
该示例缺少“ int_to_type”的定义。显然,它并没有增加答案,但是它确实意味着人们可以在快速剪切和粘贴后看到您的代码在运行。
理查德·科登

2
int_to_type的简单定义可以是:“模板<int N> struct int_to_type {};”。许多实现都将参数N值保存在枚举中,或者保持在静态整数常量中(模板<int N> struct int_to_type {enum {value = N};}; /模板<int N> struct int_to_type {static const int value = N;})
大卫·罗德里格斯(DavidRodríguez

2
只需采用boost :: integral_constant而不是int_to_type。
Vadim Ferderer 09年

2
@JohanLundberg这是指向(非静态)成员函数的指针。例如,size_t(std::vector::*p)() = &std::vector::size;
恢复莫妮卡

132

这是一个可能依赖C ++ 11功能的实现。即使继承了该功能,它也可以正确检测到该功能(不同于Mike Kinghan在其答案中观察到的答案的解决方案))。

此代码段测试的功能称为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

如果Y没有称为“序列化”的方法,这是否可行?我不知道如果方法“序列化”不存在,它将如何返回假值。
科林2014年

1
在这种情况下,@ Collin在第一次检查重载时无法替换模板参数,并将其从重载集中丢弃。返回第二个返回false_type的值。这不是编译器错误,因为SFINAE原理。
jrok 2014年

1
@ elios264没有。您可以使用宏为要检查的每个功能编写模板。
jrok 2015年

1
检查参数为T *类型而不是T或T&类型的任何特殊原因?
shibumi

1
但是如果serialize本身接受模板该怎么办。有没有一种方法可以测试是否serialize存在而无需键入确切的类型?
Hi-Angel '18年

37

关于编译时成员函数自省的问题的公认答案,尽管它很受欢迎,但存在一个障碍,可以在以下程序中观察到:

#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

如果您对这个陷阱尚不明智,那么可以看一下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*()权利,并且模板实例不“进行继承”。

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

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

作为对遵守这个约束的问题的回答,我将说明的编译时检测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? */
};

在此解决方案中,过载的SFINAE探针功能 test()是“递归调用”的。(当然,它实际上根本没有被调用;它只是由编译器解决了假设调用的返回类型。)

我们需要探究至少一处和最多两处信息:

  • 是否T::operator*()存在?如果没有,我们就完成了。
  • 鉴于T::operator*()存在,是否具有其签名 E T::operator*() const

通过评估对的单次调用的返回类型,我们得到了答案test(0,0)。通过以下方式完成:

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

此调用可能被解析为的/* SFINAE operator-exists :) */重载test(),或者可能被解析为该/* SFINAE game over :( */重载。它无法解决/* SFINAE operator-has-correct-sig :) */重载问题,因为一个人只需要一个参数,而我们要传递两个参数。

我们为什么要通过两个?只是强行将决议排除在外 /* SFINAE operator-has-correct-sig :) */。第二个参数没有其他意义。

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

让我们假设编译器对此表示是。然后 /* 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*该过载,这是单个参数类型满足E (A::*)() const,与A = T

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

如果编译器不选择/* SFINAE operator-exists :) */调用test(0,0)或不选择/* SFINAE operator-has-correct-sig :) */ 调用test(&A::operator*),则无论哪种情况,编译器都会使用 /* 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;
}

这个想法有新的缺陷吗?是否可以使它变得更通用,而不会再次避免它所造成的障碍?


16

以下是一些用法摘要:*所有这些的胆量都进一步降低了

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

巨集(El Diablo!):

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)

1
这很棒; 最好将其放在单个头文件库中。
艾伦

12

如果您知道所需的成员函数的名称,这应该就足够了。(在这种情况下,如果没有成员函数,则函数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);
}

4
!它类似于yrp提出的解决方案。我不知道模板可以通过成员函数进行模板化。这是我今天学到的新功能!...和一个新的课程:“从不说您是c ++的专家” :)
ugasoft

7

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

可运行的例子


很好,但是如果函数不带参数则
行不通

它确实很棒。将这个技巧应用于不带参数的成员函数时,我没有任何问题。
JohnB

这对我来说很有效,可以使用多个且不带方法的参数,包括重载,包含继承以及使用using从基类中带来重载。它适用于MSVC 2015和Clang-CL。但是,它不适用于MSVC 2012。
steveire

5

您可以使用std :: is_member_function_pointer

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

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

16
&A::foo如果根本没有foo,不会出现编译错误A吗?我将原始问题理解为应该与任何输入类一起使用,而不仅仅是具有某些名为的成员的类foo
Jeff Walden 2013年

5

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

  1. 同时检测继承的函数;
  2. 与非C ++ 11就绪的编译器兼容(因此没有decltype)

发现了另一个线程提出像这样的基础上,BOOST讨论。这是按照boost :: has_ *类的模型将所提出的解决方案概括为traits类的两个宏声明。

#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__

这些宏使用以下原型扩展为traits类:

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

5

为此,我们需要使用:

  1. 根据方法是否可用,具有不同返回类型的函数模板重载
  2. 为了与type_traits标头中的元条件保持一致,我们将希望从过载中返回a true_typefalse_type
  3. 声明要使用的true_type期望过载intfalse_type期望可变参数的过载:“在过载分辨率中省略号转换的最低优先级”
  4. 在定义true_type函数的模板规范时,我们将使用该函数declvaldecltype允许我们独立于方法之间的返回类型差异或重载而检测函数

您可以在此处看到一个实时示例。但我也会在下面进行解释:

我想检查是否存在一个名为的函数,该函数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))::valuetrue(请注意,无需创建特殊功能来处理void a::test()过载,void a::test(int)即被接受)
  • decltype(hasTest<b>(0))::valuetrue(因为int可以转换为double int b::test(double),与返回类型无关)
  • decltype(hasTest<c>(0))::valuefalsec不具有名为的方法testint该方法不接受可从其转换的类型,因此不接受)

该解决方案有两个缺点:

  1. 需要一对函数的每个方法声明
  2. 特别是如果我们要测试相似的名称(例如,我们要为要测试test()方法的函数命名)会造成名称空间污染?

因此,重要的是,这些函数必须在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));

您可以这样使用:

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

随后调用details::test_int<a>::valuedetails::test_void<a>::value将产生truefalse出于内联代码或元编程的目的。


3

为了避免干扰,借助Koenig lookup,您还可以放入serialize要序列化的类或归档类的名称空间。有关更多详细信息,请参见自由功能覆盖的命名空间。:-)

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


3

您似乎想要检测器惯用法。上面的答案是与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);
}

2

好的。第二次尝试。如果您也不喜欢这个,也可以,我正在寻找更多的想法。

Herb Sutter的文章讨论了特质。因此,您可以拥有一个traits类,其默认实例具有回退行为,对于成员函数所在的每个类,traits类专门用于调用成员函数。我相信Herb的文章提到了一种执行此操作的技术,这样它就不会涉及大量复制和粘贴。

但是,就像我说的那样,也许您不希望多余的工作与确实实现该成员的“标记”类有关。在这种情况下,我正在寻找第三个解决方案。


嗯...我已经分析了这个解决方案...我认为对于我的框架用户而言,这有点太昂贵了。(好的,我承认,我正在开发一个流框架,我在扩展iostream还是重写更容易的东西之间进行选择)
ugasoft

我的第三个解决方案是使用SFINAE。既然yrp的答案已经提到了它,那么我就不会继续研究它(因为我仍在研究它:我知道这个主意,但是细节决定成败),除非他的解决方案最终对您不起作用。:-)
克里斯·杰斯特·杨

1

没有C ++ 11支持(decltype),可能会起作用:

社会科学中心

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

希望如何运作

AAa以及B有关的问题,Aa是继承我们正在寻找的成员的特殊对象。

在里面 FooFindertrue_typefalse_type是对应的C ++ 11类的替代品。同样,为了理解模板元编程,它们揭示了SFINAE-sizeof-trick的基础。

TypeSink是,稍后用于所述的积分结果下沉的模板结构sizeof操作为模板实例,以形成一种类型。

match函数是SFINAE的另一种模板,没有通用副本。因此,只有当其参数的类型与它专用的类型匹配时才能实例化它。

这俩 test函数与枚举声明一起最终形成了中央SFINAE模式。有一个通用的使用省略号返回的,false_type而使用更具体的参数则优先。

为了能够test使用模板实参实例化该函数,必须实例化Tmatch函数,因为实例化该实TypeSink参需要其返回类型。需要注意的是&U::foo,包装在函数自变量中的参数不会在模板自变量专门化内进行引用,因此仍会进行继承的成员查找。


1

如果您使用的是facebook folly,则它们是开箱即用的宏,可以帮助您:

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

尽管实现细节与前面的答案相同,但是使用库更简单。


0

我也有类似的需求,并遇到了这种情况。这里提出了许多有趣/功能强大的解决方案,尽管对于特定的需求来说有点长:检测类是否具有带有精确签名的成员函数。因此,我进行了一些阅读/测试,并提出了可能感兴趣的版本。它检测到:

  • 静态成员函数
  • 非静态成员函数
  • 非静态成员函数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 <typename 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

0

基于jrok答案,我避免使用嵌套的模板类和/或函数。

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

欢迎提出建议。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.