kccqzy
kccqzy

Reputation: 1598

How to use CRTP with variadic templates?

Let's suppose originally I have the following design using 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

In this design, there is a Generator CRTP class template that performs complex calculations on the vector<int> v and prints the result at each step/part of calculation using its derived classes's befriended operator<<.

Here's an interesting concept that I want to implement: I would want outputs in multiple formats in a single execution. Specifically, I thought I could do:

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
}

The idea is that the user would use, say, AggregateOutputter<SimpleDumpOutputter, FancyOutputter and construct the object with an array of two ostreams. Whenever Generator calls operator<< on the outputter class, the AggregateOutputter will iterate through the ostreams in destinations and the types in Outputters and invoke something along the lines of *dest_iter << *static_cast<Outputter_Iter>(this);.

I'm not sure how this would work though. I'm not sure whether multiple inheritance can be used this way, whether it is possible to "zip" between an array and a pack of parameterised types. Is anyone knowledgable in this situation?

Upvotes: 1

Views: 1204

Answers (2)

kccqzy
kccqzy

Reputation: 1598

Okay, here's a solution I came up with, after being inspired by John Bandela's solution here. (see my comment on the answer for why I don't think his approach fits my needs)

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);

The idea is that in addition to the output_helper in John's answer (which I have renamed to OutputHelper), there is another auxiliary struct called OutputterHolder, which inherits from all the Outputters. I've also used an array of ostream * to store the destination of output, and modified do_output to also take an iterator so that the correct ostream can be matched.

Importantly, to accompany the change, I've changed the protected member vector<int> v in Generator to a reference, ie vector<int>& v, so that the data structure in outputter_holder can be made to refer to the structure in AggregateOutputter instead. This also requires addition of another constructor in all outputters that takes vector<int>&. The original constructor that takes the length of v would now allocate the memory using new.

I'm not sure this solution I came up with is the best/most elegant solution though.

Upvotes: 0

John Bandela
John Bandela

Reputation: 2436

I modified your original design. I thought Generator doing a bunch of calculations when the output operator is called is surprising to say the least. Also for your AggregateOutputter to output to ignore the ostream parameter of << is also surprising. Also, Outputter does not have an is-a relationship with Generator.

I tried to separate out the concerns, and ended up not using CRTP but using variadic templates, but I think it does what you want.

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);

}

Upvotes: 3

Related Questions