提问者:小点点

模板检查是否存在类成员函数?


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

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

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


共2个答案

匿名用户

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

匿名用户

这个问题是老问题,但是在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);
}

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

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

0参数用于在os<重载都可用的情况下(文字0int类型,因此第一个重载更匹配)。

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

#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)){};

活生生的例子。

接着解释。首先,sfinae_true是一个帮助器类型,它基本上等同于编写decltype(void(std::declval().stream(a0)),std::true_type{})。其优点只是它更短。
其次,结构has_stream:decltype(...)最终继承自std::true_typestd::false_type,这取决于test_stream中的decltype检查是否失败。
最后,std::declval给出了您传递的任何类型的“值”,而不需要知道如何构造它。请注意,这只能在未求值的上下文中实现,如decltypesizeof等。

注意,不一定需要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;
}

由于相同的原因,intlong参数仍然存在。数组指针用于提供可以使用sizeof的上下文。