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

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

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


当前回答

这里有很多答案,但我没有找到一个版本,它执行真正的方法解析排序,同时不使用任何较新的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'。

其他回答

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

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

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++ 20你可以写以下代码:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

是的,使用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测试了它。我不知道它是否可以移植到运行不同编译器的其他平台。

我知道这个问题已经存在多年了,但我认为对像我这样的人来说,有一个更完整的更新的答案是有用的,它也适用于const重载方法,如std::vector::begin。

基于这个答案和我接下来的问题,这里有一个更完整的答案。注意,这只适用于c++ 11及更高版本。

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T>
class has_begin
{
    private:
    has_begin() = delete;
    
    struct one { char x[1]; };
    struct two { char x[2]; };

    template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * ) ;
    template <typename C> static two test(...);    

public:
    static constexpr bool value = sizeof(test<T>(0)) == sizeof(one);
};
    
int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass::begin() exists: " << has_begin<EmptyClass>::value << std::endl;
    return 0;
}

或者更短的版本:

#include <iostream>
#include <vector>

class EmptyClass{};

template <typename T, typename = void>
struct has_begin : std::false_type {};

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
    std::cout << std::boolalpha;
    std::cout << "vector<int>::begin() exists: " << has_begin<std::vector<int>>::value << std::endl;
    std::cout << "EmptyClass exists: " << has_begin<EmptyClass>::value << std::endl;
}

注意,这里必须提供一个完整的示例调用。这意味着如果我们测试resize方法是否存在,那么我们将输入resize(0)。

深魔解释:

这个问题的第一个答案使用了test(decltype(&C::helloworld));然而,当它所测试的方法由于const重载而模棱两可时,这就有问题了,从而导致替换尝试失败。

为了解决这种模糊性,我们使用了一个void语句,它可以接受任何参数,因为它总是被转换为noop,因此模糊性是无效的,并且只要方法存在,调用就有效:

has_begin<T, decltype(void(std::declval<T &>().begin()))>

以下是依次发生的事情: 我们使用std::declval<T &>()来创建一个可调用值,然后可以调用begin。在此之后,begin的值作为参数传递给void语句。然后使用内置decltype检索该void表达式的类型,以便将其用作模板类型参数。如果begin不存在,则替换无效,根据SFINAE,将使用其他声明。