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

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

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


当前回答

我也遇到过类似的问题:

一个模板类,可以从少数基类派生,其中一些基类具有某个成员,而另一些基类没有。

我解决它类似于“typeof”(Nicola Bonelli)的答案,但使用decltype,所以它在MSVS上编译和正确运行:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

其他回答

这个问题很老了,但是在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的上下文。

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

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

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不一定是默认可构造的或满足任何特殊的概念。该技术也适用于模板成员。

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

奇怪的是,竟然没有人建议我在这个网站上看到的下面这个漂亮的把戏:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

你必须确保T是一个类。查找foo时的模糊性似乎是替换失败。我让它在gcc上工作,但不确定它是否是标准的。

可能不像其他例子那么好,但这是我为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');
}