【问题标题】:How to specialize a template without specifying a class name?如何在不指定类名的情况下专门化模板?
【发布时间】:2012-01-29 15:30:21
【问题描述】:

我想创建一个名为debug 的函数,用于输出有关对象的一些信息。我的系统包含许多不同类型的对象;其中一些包含其他对象。

using namespace std; // for brevity
struct dog {string name;};
struct human {string name; string address;};
struct line {list<human*> contents;};
struct pack {vector<dog*> contents;};

我希望函数输出参数的成员name(如果它有一个),或者调试参数的contents 成员(如果它有一个)。 我想出了以下代码:

template <class T>
void debug(T object) // T here is a simple object like dog, human, etc
{
    cout << object.name.c_str() << '\n';
}

// A helper function, not really important
template <class T>
void debug_pointer(T* object)
{
    debug(*object);
}

void debug(pack object)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<dog>);
}

void debug(line object)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<human>);
}

这里,packline 的代码几乎相同!我想避免多次编写相同的代码:

struct line {list<human*> contents; typedef human type;};
struct pack {vector<dog*> contents; typedef dog type;};

template <class T>
void debug(T object) // T here is a compound object (having contents)
{
    for_each(object.contents.begin(), object.contents.end(), debug_pointer<T::type>);
}

但是这种语法与“简单”对象的函数模板冲突(具有相同的签名)。

如何重写我的代码?我不想重写第一部分(doghuman 等的声明),因为我的程序的那部分已经非常复杂,并且添加东西(基类、成员函数等)只是为了调试似乎不合适。

【问题讨论】:

    标签: c++ templates template-specialization one-definition-rule


    【解决方案1】:

    基本代码如下所示:

    template <typename T> void debug(T const & x)
    {
        debug_helper<T, has_name<T>::value>::print(x);
    }
    

    我们需要一个辅助类:

    template <typename, bool> struct debug_helper;
    
    template <typename T> struct debug_helper<T, true>
    {
        static void print(T const & x) { /* print x.name */ }
    };
    template <typename T> struct debug_helper<T, false>
    {
        static void print(T const & x) { /* print x.content */ }
    };
    

    现在我们只需要一个 SFINAE 特征类 has_name&lt;T&gt;,以及一个打印容器的机制。这两个问题在pretty printer code 中几乎一字不差地解决了。

    【讨论】:

      【解决方案2】:

      使容器也成为模板参数:

      template <template <typename> class Container, typename T>
      void debug(Container<T> object)
      {
          for_each(object.contents.begin(), object.contents.end(), debug_pointer<T>);
      }
      

      顺便说一句,大多数情况下您可能希望通过 const 引用而不是按值传递(这需要复制整个向量/列表):

      template <template <typename> class Container, typename T>
      void debug(const Container<T>& object)
      

      如果可以使用C++11,可以使用decltype从内容中确定T

      template <typename T>
      void debug(const T& object)
      {
          typedef decltype(*object.contents.front()) T;
          for_each(object.contents.begin(), object.contents.end(), debug_pointer<T>);
      }
      

      GCC also has typeof 无法使用 C++11 时。

      【讨论】:

      • 实际上,我的“简单”类也是模板化的。我想通过成员contents 的存在来选择debug 的版本,而不是通过参数的“模板化”(也编辑了我的问题)。
      • @anatolyg:你要么需要typedef human type,要么使用decltype/typeof
      【解决方案3】:

      使用 C++11、decltype 和 SFINAE 让事情变得简单 :)

      #include <string>
      #include <vector>
      #include <list>
      #include <iostream>
      #include <algorithm>
      
      struct dog { std::string name; };
      struct human { std::string name; std::string address; };
      struct line { std::list<human*> contents; };
      struct pack { std::vector<dog*> contents; };
      
      template <typename T>
      auto debug(T const& t) -> decltype(t.name, void(0)) {
        std::cout << t.name << '\n';
      }
      
      template <typename T>
      auto debug(T const* t) -> decltype(t->name, void(0)) {
        if (t != 0) std::cout << t->name << '\n';
      }
      
      struct Debugger {
        template <typename T>
        void operator()(T const& t) { debug(t); }
      };
      
      template <typename C>
      auto debug(C const& c) -> decltype(c.contents, void(0)) {
        typedef decltype(c.contents) contents_type;
        typedef typename contents_type::value_type type;
        std::for_each(c.contents.begin(), c.contents.end(), Debugger());
      }
      
      
      int main() {
        dog dog1 = { "dog1" }, dog2 = { "dog2" };
        human h1 = { "h1" }, h2 = { "h2" };
      
        line l; l.contents.push_back(&h1); l.contents.push_back(&h2);
      
        debug(l);
      
      }
      

      ideone 的操作中产生:

      h1
      h2
      

      如预期的那样:)

      如果没有 C++11,它需要一些小技巧,但原理保持不变,使用 boost::enable_if 您需要创建一个结构,该结构将根据 name 和 @987654327 的存在和可访问性引发编译错误@。

      当然,如果你简单地将方法连接到结构本身中会更容易:)

      【讨论】:

        【解决方案4】:

        您可以使用 SFINAE 选择正在使用的重载。

        我忘记了确切的细节,但您可以使用它来检测“内容”成员或“名称”成员的存在,然后基于此进行重载。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-10-13
          • 2015-06-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多