【问题标题】:How to use CRTP with variadic templates?如何将 CRTP 与可变参数模板一起使用?
【发布时间】:2013-04-06 19:21:47
【问题描述】:

假设最初我使用 CRTP 进行了以下设计:

template<class Outputter> class Generator {
protected:
    vector<int> v;
private:
    void work(ostream& out) {
        // perform first part of some complex operations on v
        out << *static_cast<Outputter *>(this);
        // perform second part of some complex operations on v
        out << *static_cast<Outputter *>(this);
        // many more ....
        // perform some final actions
    }
public:
    Generator(unsigned length): v(length) {}
    friend ostream& operator<<(ostream& out, Outputter&& generator) {
        // perform some preparation work
        work(out);
        // perform some final actions
        return out;
    }
};

class SimpleDumpOutputter : public Generator<SimpleDumpOutputter> {
private:
    unsigned count;
public:
    SimpleDumpOutputter(unsigned length): Generator(length), count() {}
    friend ostream& operator<<(ostream& out, SimpleDumpOutputter& outputter) {
        out << "Step " << ++count << " of calculation: "
        copy(outputter.v.begin(), outputter.v.end(), ostream_iterator<int>(out, " "));
        out << endl;
        return out;
    }
};

class FancyOutputter : public Generator<FancyOutputter> { // create a graph using graphviz's dot language to visualise v
private:
    // abbreviated
public:
    FancyOutputter(unsigned length): Generator(length) {}
    friend ostream& operator<<(ostream& out, FancyOutputter& outputter) {
        // write statements to out
        return out;
    }
};

// some more different Outputters, for example an Outputter that creates a pretty LaTeX document

在这个设计中,有一个Generator CRTP 类模板,它对vector&lt;int&gt; v 执行复杂的计算,并使用其派生类的好友operator&lt;&lt; 在计算的每个步骤/部分打印结果。

我想实现一个有趣的概念:我希望在一次执行中以多种格式输出。具体来说,我认为我可以这样做:

template<class Outputters> class AggregateOutputter : public Generator<AggregateOutputter<Outputters...> > {
private:
    static const unsigned outputter_count = sizeof...(Outputters);
    typedef array<ostream *, outputter_count> DestArr;
    DestArr destinations;
public:
    AggregateOutputter(unsigned v_length, DestArr destinations): IsomerGenerator<AggregateOutputter<Outputters...> >(length), destinations(destinations) {}
    friend ostream& operator<<(ostream&, AggregateOutputter& outputter); // first argument is dummy, because we would use the ostreams in destinations
}

这个想法是用户会使用,比如说,AggregateOutputter&lt;SimpleDumpOutputter, FancyOutputter,并用两个ostreams 的array 来构造对象。每当Generator 在输出类上调用operator&lt;&lt; 时,AggregateOutputter 将遍历destinations 中的ostreams 和Outputters 中的类型,并调用类似于*dest_iter &lt;&lt; *static_cast&lt;Outputter_Iter&gt;(this); 的内容。

我不确定这将如何工作。我不确定是否可以以这种方式使用多重继承,是否可以在array 和一组参数化类型之间“压缩”。有没有人了解这种情况?

【问题讨论】:

  • 在您的原始版本中,您的operator &lt;&lt; 重载为friends,但使用的是this。这不会编译
  • @AndyProwl 我很抱歉。我试图简化生产代码但犯了一个错误。其实operator&lt;&lt;调用私有成员函数work做实际工作。
  • 你可以使用索引来压缩数组和类型列表。
  • @Pubby 那么你可以指定一个数字索引来访问类型包中的类型吗?我以前认为必须使用递归来“迭代”类型包。您是否建议类似于tuple 提供get&lt;N&gt; 的方式?
  • @kccqzy 它构造了一个 int 参数包,您可以使用 ... 对其进行扩展,请参见:loungecpp.wikidot.com/tips-and-tricks:indices

标签: c++ templates c++11 variadic-templates crtp


【解决方案1】:

我修改了你原来的设计。我认为在调用输出运算符时生成器进行大量计算至少可以说是令人惊讶的。此外,您的 AggregateOutputter 输出忽略

我试图分离出关注点,但最终没有使用 CRTP,而是使用可变参数模板,但我认为它可以满足您的需求。

http://ideone.com/xQrnW4

#include <vector>
#include <iostream>
#include <iterator>
#include <array>
using namespace std;

class Generator {
protected:
    vector<int> v;
public:
    Generator(unsigned length): v(length) {}

    template<class Outputter>
    void do_calculations_with_output(Outputter& out){
        // perform first part of some complex operations on v
        out.output(v);
        // perform second part of some complex operations on v
        out.output(v);
        // perform some final actions
    }

};

class SimpleDumpOutputter {
private:

    ostream* out;
    unsigned count;
public:
    SimpleDumpOutputter(ostream& os): out(&os), count() {}
    template<class C>
    void output(const C& c) {
        *out << "Step " << ++count << " of calculation: ";
        copy(c.begin(),c.end(), ostream_iterator<int>(*out, " "));
        *out << endl;
    }
};

class FancyOutputter {
    ostream* out;
    int count;
public:
    FancyOutputter(ostream& os): out(&os),count() {}
    template<class C>
    void output(const C& c) {
        // create a graph using graphviz's dot language to ease visualisation of v
        *out << "Step " << ++count << " of calculation: ";
       *out << "Graphviz output\n";
    }
};

template<class... Outputters> class AggregateOutputter : private Outputters... {
private:
   template<class First, class... Rest>
   struct output_helper{
      template<class C>
      static void do_output(AggregateOutputter* pthis,const C& c){
          static_cast<First*>(pthis)->output(c);
          output_helper<Rest...>::do_output(pthis,c);
      }

   };

   template<class First>
   struct output_helper<First>{
      template<class C>
      static void do_output(AggregateOutputter* pthis,const C& c){
          static_cast<First*>(pthis)->output(c);
      }

   };
public:
   template<class... Out>
    AggregateOutputter( Out&... out): Outputters(out)...{}
    template<class C>
    void output(const C& c) {
        output_helper<Outputters...>::do_output(this,c);
    }

};
int main(){

    AggregateOutputter<FancyOutputter,SimpleDumpOutputter> out(cout,cout);

    Generator g(10);

    g.do_calculations_with_output(out);

}

【讨论】:

  • 非常感谢您的回答。我确实认为我的设计看起来有点奇怪,这是我的理由:在调用 operator&lt;&lt; 时进行大量计算是某种“懒惰的评估”。由于计算很长,因此在用户指定一个输出器来格式化输出以及将格式化的输出发送到的ostream 之前,不应进行计算。在您的示例中,生成器在调用do_calculations_with_output 时进行计算,因此原理相同,只是我添加了语法糖并使用operator&lt;&lt; 而不是函数。
  • 我之所以使用 CRTP 而不是将输出器传递给生成器,是因为输出器非常复杂,需要调用生成器的protected 成员函数来完成部分工作。跨度>
【解决方案2】:

好的,这是我在受到 John Bandela 的解决方案 here 的启发后提出的解决方案。 (请参阅我对答案的评论,了解为什么我认为他的方法不符合我的需求)

template<class... Outputters> class AggregateOutputter : public Generator<AggregateOutputter<Outputters...> > {
private:
    typedef array<ostream *, sizeof...(Outputters)> DestArr;
    DestArr destinations;
    typedef typename DestArr::iterator DestArrIter;
    struct OutputterHolder : public Outputters... {
        OutputterHolder(vector<int>& v): Outputters(v)... {}
    } outputter_holder;
    template<class First, class... Rest> struct OutputHelper {
        static void do_output(OutputterHolder *pthis, DestArrIter dest) {
            **dest << *static_cast<First *>(pthis);
            OutputHelper<Rest...>::do_output(pthis, ++dest);
        }
    };
    template<class First> struct OutputHelper<First> {
        static void do_output(OutputterHolder *pthis, DestArrIter dest) {
            **dest << *static_cast<First *>(pthis);
        }
    };
public:
    template<typename... OstreamStar> AggregateOutputter(unsigned length, OstreamStar... ostreams): Generator<AggregateOutputter<Outputters...> >(length), destinations{{ostreams...}}, outputter_holder(this->v) {
        static_assert(sizeof...(OstreamStar) == sizeof...(Outputters), "number of outputters and destinations do not match");
    }
    friend ostream& operator<<(ostream& dummy_out, AggregateOutputter& outputter) {
        OutputHelper<Outputters...>::do_output(&outputter.outputter_holder, outputter.destinations.begin());
        // possibly write some logging info to dummy_out
        return dummy_out;
    }
};

// to use this:
ofstream fout("gv.gv");
cout << AggregateOutputter<FancyOutputter, SimpleDumpOutputter>(length, &fout, &cout);

这个想法是除了约翰回答中的output_helper(我已将其重命名为OutputHelper)之外,还有另一个名为OutputterHolder的辅助struct,它继承自所有Outputters。我还使用了ostream * 中的array 来存储输出的目的地,并将do_output 修改为也使用iterator,以便匹配正确的ostream

重要的是,伴随着变化,我把Generator中的protected member vector&lt;int&gt; v改为引用,即vector&lt;int&gt;&amp; v,这样outputter_holder中的数据结构就可以引用结构了在AggregateOutputter 代替。这还需要在所有采用vector&lt;int&gt;&amp; 的输出器中添加另一个构造函数。长度为v 的原始构造函数现在将使用new 分配内存。

我不确定我想出的这个解决方案是最好/最优雅的解决方案。

【讨论】:

    猜你喜欢
    • 2014-08-30
    • 1970-01-01
    • 2023-04-07
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多