Select at runtime to implement CRTP

I would like to use static polymorphism using the CRTP idiom, being able to choose at runtime which implementation to use. Let me give you an example:

I have several classes responsible for the computation:

template<typename Implementation>
class FooInterface {
public:
  void compute(){
    (static_cast<Implementation*>(this))->compute();
  }
};

class FooForward : public FooInterface<FooForward> {
public:
  void compute(){
    //do stuff
  }
};

class FooBackward : public FooInterface<FooBackward> {
public:
  void compute(){
    //do other stuff
  }
};

      

and

template<typename Implementation>
class BarInterface {
public:
  void eval(){
    (static_cast<Implementation*>(this))->eval();
  }
};

class BarForward : public BarInterface<BarForward> {
public:
  void eval(){
    //do something
  }
};

class BarBackward : public BarInterface<BarBackward> {
public:
  void eval(){
    //do something else
  }
};

      

Now I want to use these objects as members of another class, call it Model

and use them in a loop:

template<typename Foo, typename Bar>
class Model {
private:
  Foo* foo_;
  Bar* bar_;
  int max_iter_;

public:
  Model<Foo, Bar>(int max_iter) : max_iter_(max_iter){
    foo_ = new Foo();
    bar_ = new Bar();
  }

  void solve(){
    for(int i = 0; i < max_iter_; ++i){
      foo_->compute();
      bar_->eval();
    }
  }
};

      

Note that a function Model::solve()

is iterated over a lot and performance in my application is critical, so using CRTP instead of dynamic polymorphism to prevent virtual function calls and enable compiler nesting.

Now my problem arises when I want the user to decide which implementation FooInterface

and BarInterface

use at runtime. In mine main.cpp

I have:

int main(int argc, char** argv){
  /*
   * Here an input file is read into a map which looks like this
   * std::map<std::string, std::string> settings
   */
  // Here I need a way to choose, based on settings, what will Foo and Bar be
  Model<Foo, Bar> model;
  model.solve();
}

      

I thought of some kind of factory that could return the correct one Model

, but I don't know what the return type might be, and how I imagine it, it's not convenient, because in my application I have more than 2 template parameters, and then the number of combinations becomes very large

class Factory{
  /*type?*/ createModel(std::map<std::string, std::string> settings){
    if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
      Model<FooForward, BarForward>* model = new Model<FooForward, BarForward>();
      return model;
    }
    else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
      Model<FooForward, BarBackward>* model = new Model<FooForward, BarBackward>();
      return model;
    }
    else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
      Model<FooBackward, BarForward>* model = new Model<FooBackward, BarForward>();
      return model;
    }
    else {
      Model<FooBackward, BarBackward>* model = new Model<FooBackward, BarBackward>();
      return model;
    }
  }
};

      

As I imagine, all combinations of patterns will be compiled and the user can choose at runtime which one to use. Is there a way to accomplish this using CRTP?

+3


source to share


1 answer


Regarding the factory method, I think there is no way to define one type, since the type information is needed at compile time and the actual settings will only be known at runtime.

But if you use a variant, you can combine all the possible return types into one. This type can then be returned by the factory method:

class Factory{

public:

    using ModelVariant = boost::variant
    <
        Model< FooBackward , BarBackward > ,
        Model< FooBackward , BarForward > ,
        Model< FooForward , BarBackward > ,
        Model< FooForward , BarForward >
    >;

    static ModelVariant createModel(std::map<std::string, std::string> settings , int i)
    {
        if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
          Model<FooForward, BarForward> model = Model<FooForward, BarForward>(i);
          return model;
        }
        else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
          Model<FooForward, BarBackward> model = Model<FooForward, BarBackward>(i);
          return model;
        }
        else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
          Model<FooBackward, BarForward> model = Model<FooBackward, BarForward>(i);
          return model;
        }
        else// ((settings["foo"] == "bwd") && (settings["bar"] == "bwd"))
        {
          Model<FooBackward, BarBackward> model = Model<FooBackward, BarBackward>(i);
          return model;
        }
    }
};

      

But now you need the visitor to actually call the required method solve()

:

auto model { Factory::createModel( settings , 1 ) };

boost::apply_visitor( [ ]( auto & m ){ m.solve(); } , model );
//     > FooForward::compute()
//     > BarBackward::eval()

      

Live at coliru

Also, without resorting to any kind of metaprogramming of the template, it becomes very difficult if you keep adding different implementations of Foo

and Bar

.




Original answer:

Perhaps you could use a static template variable and a simple function:

std::map< std::string , std::string > settings
{
    { "foo" , "fwd" } ,
    { "bar" , "bwd" }
};

template< typename F , typename B>
static Model< F , B > m( 1 );

void solve()
{
    if ((settings["foo"] == "fwd") && (settings["bar"] == "fwd")){
      m<FooForward, BarForward>.solve();
    }
    else if ((settings["foo"] == "fwd") && (settings["bar"] == "bwd")){
      m<FooForward, BarBackward>.solve();
    }
    else if ((settings["foo"] == "bwd") && (settings["bar"] == "fwd")){
      m<FooBackward, BarForward>.solve();
    }
    else// ((settings["foo"] == "bwd") && (settings["bar"] == "bwd"))
    {
      m<FooBackward, BarBackward>.solve();
    }
}

int main()
{
    // Load settings somehow

    solve(); // > FooForward::compute()
             // > BarBackward::eval()

}

      

Live at coliru

You can hide these things in an anonymous namespace in translation unity where you use them for better encapsulation and avoid using a factory.

+4


source







All Articles