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

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

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您可以检查给定的类是否提供了特定的方法。下面是工作代码:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( decltype(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
    
int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

我刚刚用Linux和gcc 4.1/4.3测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。

其他回答

下面是我的版本,它可以任意处理所有可能的成员函数重载,包括模板成员函数,可能带有默认实参。当成员函数调用某个类类型时,它区分了3种互斥的情况,给定的arg类型:(1)有效,或(2)模糊,或(3)不可用。使用示例:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

现在你可以这样使用它:

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

下面是用c++11编写的代码,但是,你可以很容易地将它移植到具有typeof扩展的非c++11(例如gcc)。你可以用你自己的宏替换HAS_MEM宏。

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif

好吧,这个问题已经有一长串的答案了,但是我想强调一下Morwenn的评论:c++ 17有一个提案让它变得非常简单。有关详细信息,请参阅N4502,但作为一个自包含的示例,请考虑以下内容。

这部分是常量部分,放在头文件中。

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

然后是变量部分,在此指定要查找的内容(类型、成员类型、函数、成员函数等)。就OP而言:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

下面的例子取自N4502,展示了一个更精细的探针:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

与上面描述的其他实现相比,这个实现相当简单:减少了工具集(void_t和detect)就足够了,不需要复杂的宏。此外,据报道(参见N4502),它比以前的方法更有效(编译时间和编译器内存消耗)。

这里有一个活生生的例子。它与Clang一起工作得很好,但不幸的是,5.1之前的GCC版本遵循了对c++ 11标准的不同解释,这导致void_t不能按预期工作。Yakk已经提供了解决方案:使用以下定义的void_t (void_t在参数列表中有效,但不能作为返回类型):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

c++ 20 -需要表达式

c++ 20带来了一些概念和各种工具,比如require表达式,这是一种检查函数是否存在的内置方式。有了它们,你可以重写optionalToString函数如下:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

pre - c++ 20 -检测工具包

N4502 proposes a detection toolkit for inclusion into the C++17 standard library that eventually made it into the library fundamentals TS v2. It most likely won't ever get into the standard because it has been subsumed by requires expressions since, but it still solves the problem in a somewhat elegant manner. The toolkit introduces some metafunctions, including std::is_detected which can be used to easily write type or function detection metafunctions on the top of it. Here is how you could use it:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

注意,上面的例子是未经测试的。标准库中还没有检测工具包,但建议包含了一个完整的实现,如果您确实需要它,可以很容易地复制它。它可以很好地使用c++ 17的特性,如果constexpr:

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

C++14 - 助推哈娜

提振。Hana显然建立在这个特定的例子之上,并在其文档中提供了c++ 14的解决方案,所以我将直接引用它:

[...] Hana provides a is_valid function that can be combined with C++14 generic lambdas to obtain a much cleaner implementation of the same thing: auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { }); This leaves us with a function object has_toString which returns whether the given expression is valid on the argument we pass to it. The result is returned as an IntegralConstant, so constexpr-ness is not an issue here because the result of the function is represented as a type anyway. Now, in addition to being less verbose (that's a one liner!), the intent is much clearer. Other benefits are the fact that has_toString can be passed to higher order algorithms and it can also be defined at function scope, so there is no need to pollute the namespace scope with implementation details.

提振。创科实业

执行这种检查的另一个惯用工具包是Boost,尽管它没有那么优雅。TTI,在Boost 1.54 4.0中引入。对于您的示例,您必须使用宏BOOST_TTI_HAS_MEMBER_FUNCTION。下面是你如何使用它:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

然后,您可以使用bool来创建SFINAE检查。

解释

宏BOOST_TTI_HAS_MEMBER_FUNCTION生成元函数has_member_function_toString,该函数将选中的类型作为其第一个模板参数。第二个模板形参对应于成员函数的返回类型,下面的形参对应于函数形参的类型。如果类T有成员函数std::string toString(),则成员值为true。

或者,has_member_function_toString可以接受成员函数指针作为模板形参。因此,可以将has_member_function_toString<T, std::string>::value替换为has_member_function_toString<std::string T::* ()>::value。

这里有很多答案,但我没有找到一个版本,它执行真正的方法解析排序,同时不使用任何较新的c++特性(只使用c++98特性)。 注意:此版本已测试,并使用vc++2013, g++ 5.2.0和在线编译器。

所以我提出了一个版本,只使用sizeof():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

现场演示(带有扩展的返回类型检查和vc++2010解决方案):http://cpp.sh/5b2vs

没有消息来源,因为是我自己想出来的。

在g++编译器上运行Live演示时,请注意数组大小为0是允许的,这意味着使用static_assert将不会触发编译器错误,即使它失败了。 一个常用的解决方法是将宏中的'typedef'替换为'extern'。

下面是工作代码的示例。

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptr将启用带有额外int参数的函数,该函数的优先级高于使用0调用时需要很长时间的函数。

你可以对函数使用相同的原则,如果函数被实现,返回true。

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}