是否有可能编写一个模板,根据某个成员函数是否定义在类上而改变行为?

下面是我想写的一个简单的例子:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

因此,如果类T定义了toString(),那么它就使用它;否则,它就不会。我不知道如何做的神奇部分是“FUNCTION_EXISTS”部分。


当前回答

我在另一个线程中对此写了一个答案(与上面的解决方案不同),也检查继承的成员函数:

SFINAE检查继承的成员函数

以下是该解决方案的一些例子:

例二:

我们正在检查一个具有以下签名的成员: T::const_iterator begin(

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

请注意,它甚至检查方法的常量,并且也适用于基本类型。(我的意思是has_const_begin<int>::value为false,不会导致编译时错误。)

示例2

现在我们正在寻找签名:void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

请注意,MyClass不一定是默认可构造的或满足任何特殊的概念。该技术也适用于模板成员。

我急切地等待有关这方面的意见。

其他回答

MSVC有__if_exists和__if_not_exists关键字(Doc)。连同Nicola的typef - sfinae方法,我可以创建一个检查GCC和MSVC,就像OP所寻找的那样。

更新:来源可以在这里找到

我修改了https://stackoverflow.com/a/264088/2712152中提供的解决方案,使其更加通用。此外,由于它不使用任何新的c++ 11特性,我们可以将它与旧的编译器一起使用,并且应该也可以与msvc一起使用。但是编译器应该允许C99使用这个,因为它使用可变宏。

下面的宏可用于检查特定类是否具有特定类型定义。

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

下面的宏可以用来检查一个特定的类是否有一个特定的成员函数,是否有给定数量的参数。

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

我们可以使用上面的两个宏来检查has_typedef和has_mem_func:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

这是我在c++ 20中发现的最简洁的方法,非常接近你的问题:

template<class T>
std::string optionalToString(T* obj)
{
  if constexpr (requires { obj->toString(); })
    return obj->toString();
  else
    return "toString not defined";
}

在godbolt上观看:https://gcc.godbolt.org/z/5jb1d93Ms

这是c++ 17中另一种实现方法(灵感来自boost:hana)。

该解决方案不需要has_something<T> SFINAE类型trait类。

解决方案

////////////////////////////////////////////
// has_member implementation
////////////////////////////////////////////

#include <type_traits>

template<typename T, typename F>
constexpr auto has_member_impl(F&& f) -> decltype(f(std::declval<T>()), true)
{
  return true;
}

template<typename>
constexpr bool has_member_impl(...) { return false; }

#define has_member(T, EXPR) \
 has_member_impl<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Test

////////////////////////////////////////////
// Test
////////////////////////////////////////////

#include <iostream>
#include <string>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from Example::toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(has_member(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(has_member(Example, Foo), 
                  "Example class must have Foo member");
    static_assert(has_member(Example, Bar()), 
                  "Example class must have Bar() member function");
    static_assert(!has_member(Example, ZFoo), 
                  "Example class must not have ZFoo member.");
    static_assert(!has_member(Example, ZBar()), 
                  "Example class must not have ZBar() member function");

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

下面是一些用法片段: *所有这些的核心都在更深处

在给定的类中检查成员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;

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

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;

细节和核心:

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