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

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

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”部分。


当前回答

这就是类型特征存在的意义。不幸的是,它们必须手动定义。在你的情况下,想象一下:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

其他回答

可能不像其他例子那么好,但这是我为c++ 11想出的。这适用于选择重载方法。

template <typename... Args>
struct Pack {};

#define Proxy(T) ((T &)(*(int *)(nullptr)))

template <typename Class, typename ArgPack, typename = nullptr_t>
struct HasFoo
{
    enum { value = false };
};

template <typename Class, typename... Args>
struct HasFoo<
    Class,
    Pack<Args...>,
    decltype((void)(Proxy(Class).foo(Proxy(Args)...)), nullptr)>
{
    enum { value = true };
};

示例使用

struct Object
{
    int foo(int n)         { return n; }
#if SOME_CONDITION
    int foo(int n, char c) { return n + c; }
#endif
};

template <bool has_foo_int_char>
struct Dispatcher;

template <>
struct Dispatcher<false>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n) + c;
    }
};

template <>
struct Dispatcher<true>
{
    template <typename Object>
    static int exec(Object &object, int n, char c)
    {
        return object.foo(n, c);
    }
};

int runExample()
{
    using Args = Pack<int, char>;
    enum { has_overload = HasFoo<Object, Args>::value };
    Object object;
    return Dispatcher<has_overload>::exec(object, 100, 'a');
}

这个问题很老了,但是在c++ 11中,我们有了一种新的方法来检查函数是否存在(或者任何非类型成员是否存在),再次依赖SFINAE:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

现在来解释一下。首先,我使用表达式SFINAE从重载解析中排除序列化(_imp)函数,如果decltype中的第一个表达式无效(即函数不存在)。

void()用于使所有这些函数的返回类型为空。

如果os << obj重载都可用,则使用0参数优先选择os << obj重载(字面量0是int类型,因此第一个重载是更好的匹配)。


现在,您可能需要一个trait来检查函数是否存在。幸运的是,这很容易写出来。不过,请注意,您需要为可能需要的每个不同函数名自己编写trait。

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

生活的例子。

And on to explanations. First, sfinae_true is a helper type, and it basically amounts to the same as writing decltype(void(std::declval<T>().stream(a0)), std::true_type{}). The advantage is simply that it's shorter. Next, the struct has_stream : decltype(...) inherits from either std::true_type or std::false_type in the end, depending on whether the decltype check in test_stream fails or not. Last, std::declval gives you a "value" of whatever type you pass, without you needing to know how you can construct it. Note that this is only possible inside an unevaluated context, such as decltype, sizeof and others.


注意,decltype不一定是必需的,因为sizeof(以及所有未求值的上下文)得到了增强。只是decltype已经交付了一个类型,因此更简洁。下面是其中一个重载的sizeof版本:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

由于同样的原因,int和long形参仍然存在。数组指针用于提供可以使用sizeof的上下文。

下面是我的版本,它可以任意处理所有可能的成员函数重载,包括模板成员函数,可能带有默认实参。当成员函数调用某个类类型时,它区分了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
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}