首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >模板化检查类成员函数是否存在?

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

Stack Overflow用户
提问于 2008-11-02 20:10:48
回答 33查看 198.4K关注 0票数 575

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

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

代码语言:javascript
运行
复制
template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

所以,如果class T定义了toString(),那么它就会使用它;否则,它就不会使用它。

EN

回答 33

Stack Overflow用户

回答已采纳

发布于 2008-11-02 21:15:01

可以,使用SFINAE可以检查给定的类是否提供了特定的方法。下面是工作代码:

代码语言:javascript
运行
复制
#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测试了它。我不知道它是否可以移植到其他运行不同编译器的平台上。

票数 362
EN

Stack Overflow用户

发布于 2012-02-06 08:27:05

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

代码语言:javascript
运行
复制
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);
}

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

void()用于将所有这些函数的返回类型设置为void

如果两个参数都可用,则0参数用于首选os << obj重载(文字0的类型为int,因此第一个重载是更好的匹配)。

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

代码语言:javascript
运行
复制
#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)){};

Live example.

接下来是解释。首先,sfinae_true是一种帮助器类型,它基本上等同于编写decltype(void(std::declval<T>().stream(a0)), std::true_type{})。优点很简单,那就是它更短。

接下来,struct has_stream : decltype(...)最终继承自std::true_typestd::false_type,这取决于decltypetest_stream中的检查是否失败。

最后,std::declval为您提供了您传递的任何类型的“值”,而您不需要知道如何构造它。请注意,这只能在未计算的上下文中实现,例如decltypesizeof等。

请注意,不一定需要decltype,因为sizeof (和所有未评估的上下文)都得到了增强。只是decltype已经提供了一种类型,因此更简洁。下面是其中一个重载的sizeof版本:

代码语言:javascript
运行
复制
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

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

票数 286
EN

Stack Overflow用户

发布于 2008-11-05 01:08:39

C++允许使用SFINAE来实现这一点(请注意,对于C++11特性,这更简单,因为它支持几乎任意表达式上的扩展SFINAE -以下代码是为使用常见的C++03编译器而精心设计的):

代码语言:javascript
运行
复制
#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

上面的模板和宏试着实例化一个模板,给它一个成员函数指针类型和实际的成员函数指针。如果类型不适合,SFINAE会导致模板被忽略。用法如下:

代码语言:javascript
运行
复制
HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

但请注意,您不能只在if分支中调用该toString函数。由于编译器将在两个分支中检查有效性,因此在函数不存在的情况下将失败。一种方法是再次使用SFINAE (enable_if也可以从boost获得):

代码语言:javascript
运行
复制
template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

祝你玩得开心。它的优点是,它也适用于重载的成员函数,也适用于const成员函数(记得使用std::string(T::*)() const作为成员函数指针类型!)。

票数 166
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/257288

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档