Freddie Witherden
Freddie Witherden

Reputation: 2426

C++ Looking up Types At Runtime

I have a class parameterised by some template parameters:

template<typename Scalar, typename Integrator, int Dimension>
class foo;

Each of the template parameters can be one of a few possible types. Currently the type of foo used is hard-coded in man typedef foo<...> foo_type. I wish to adapt my program so that a collection of foo's are supported; something like:

if (desired_foo_str == "2DSimpleFloat")
{
    foo<float,Simple,2>(params).method();
}
else if (desired_foo_str == "3DSimpleDouble")
{
    foo<double,Simple,3>(params).method();
}
else
{
    std::cout << "Unsupported foo."
}

The interface of foo does not depend on its template parameters. My question is how can I improve this solution? I know boost::mpl provides a type vector but it seems more for compile time reductions as opposed to run-time switching.

Clarification

Lets say (this is a simplification) that my program takes a set of points in N-dimensions (provided by the user) and integrates them. Certain combinations of dimensions, integration methods and scalar types can be accelerated by SIMD (hence the use of template parameters). All combinations of foo<A,B,N> are valid however different users (all of whom will have compiled my program) will require only a couple of specific specializations for their work. I wish to allow for:

$ integrate --method=2DSimpleFloat mypoints2d.dat
$ integrate --methid=3DSimpleDouble mypoints3d.dat

so run-time selection of what method they wish to use. I am wondering what kind of frame-work best allows me to associate types with strings such that I can better handle the above scenario.

Upvotes: 3

Views: 821

Answers (3)

centaurian_slug
centaurian_slug

Reputation: 3399

You could make templated default method which throws an error, and template-specializations per combination that you support.

class Simple {};
template<typename Scalar, typename Integrator, int Dimension>
class foo
{
public:
  void method();
  foo() {}
};

// default implementation throws an error
template<typename Scalar, typename Integrator, int Dimension>
void foo<Scalar,Integrator,Dimension>::method() {  cout << "unsupported\n"; };

// override default for supported cases:-
template<>
void foo<double,Simple,2>::method() { cout <<"method1\n"; };

template<>
void foo<double,Simple,3>::method() { cout <<"method2\n"; };

// test program
void main() {
   foo<float,Simple,2> a; a.method(); // output "unsupported"
   foo<double,Simple,2> b; b.method(); // output "method1"
   foo<double,Simple,3> c; c.method(); // output "method2"
}

You should be able to mix general purpose implementations and special purpose overides freely throughout the class; (e.g. perhaps some permeation can be handled with SIMD intrinsics or whatever)

If all the class methods were identical and generic, a convenient way to restrict use might be to restrict the constructor so that undesired cases can't be instantiated

in general if the mechanisms of overloading and templates are being used correctly, you should be able to avoid checking types manually where they're used. This can all work compile time statically linked without any pointers or virtual dispatch.

If the supported implementations are to be the same, the over-rides can be wrappers to direct to another templated method as suggested above.

Upvotes: 2

Arpegius
Arpegius

Reputation: 5887

I'm guesing you are looking for register pattern. This is only my draft, so don't rely on it.

class AbstractFooFactory
{
     virtual AbstractFoo* create( ParamsType cons& params ) = 0;
     // or construct on stack and call .method()
     virtual void createAndCallMethod( ParamsType cons& params ) = 0;
};

class FooRegister
{
   ~FooRegister(); // delete all pointers
   template< typename FooFactory >
   void operator() ( FooFactory const & factory ) // for boost::mpl:for_each 
   { map[factory.getName()]= new FooFactory( factory ); }
   AbstractFooFactory* get( std::string name );
   std::map< std::string , AbstractFooFactory* > map;
};

template< typename Scalar, typename Integrator, typename Dimension >
class FooFactory: public AbstractFooFactory
{
    typedef FooFactory<Scalar, Integrator, Dimension > type; // Metafunction
    std::string getName(); // this will be a bit hard to implement
    AbstractFoo* create( ParamsType cons& params );
    void createAndCallMethod( ParamsType cons& params );
};

Simple trails may be used for storing type names:

template< typename Type >
struct NameTrails
{
    static const char const* value;
};

template<> const char const* NameTrails<int>::value = "Int";
template<> const char const* NameTrails<float>::value = "Float";
template<> const char const* NameTrails<double>::value = "Double";
template<> const char const* NameTrails<Simple>::value = "Simple";
template<> const char const* NameTrails<Complex>::value = "Complex";

template< typename Scalar, typename Integrator, typename Dimension >
std::string FooFactory::getName() 
{
  return boost::lexical_cast<std::string>( Dimension::value ) + "D"
         + NameTrails< Integrator >::value
         + NameTrails< Scalar >::value;
}

And now you need to register all types using mpl::for_each:

FooRegister fooRegister;

typedef boost::mpl::vector<Simple,Complex> IntegratorsList;
typedef boost::mpl::vector<int,float,double> ScalarsList;
typedef boost::mpl::range_c<int,1,4> DimensionsList;

typedef boost::mpl::vector< 
     boost::mpl::vector< Simple, float, boost::mpl::int_<2> >,
     boost::mpl::vector< Simple, double, boost::mpl::int_<3> >,
    ... other types or full cross join ... > FooList;

boost::mpl::for_each< FooList, boost::mpl::quote3<FooFactory> >( 
    boost::ref(fooRegister) );

What i don't know is how to cross join IntegratorsList, ScalarList, range_c<int,1,4> to constuct full FooList.

fooRegister.get("2DSimpleFloat")->createAndCallMethod(params);

You probably want to do this statically, so yes it is possible, but i find it rather difficult to achieve better performance then a simple dynamic map or hash map.

Upvotes: 0

Kerrek SB
Kerrek SB

Reputation: 476930

Your question doesn't provide enough information for a complete answer, but I have a hunch: Perhaps you should look into refactoring your code so as to separate the part that is independent of the parameters from the code that depends on the template parameters.

The typical example is taken from Scott Meyers's book. Suppose you have a square matrix multiplicator, and you write this as a full template:

template <typename T, unsigned int N>
Matrix<T, N> multiply(Matrix<T, N>, Matrix<T, N>)
{
   // heavy code
}

With this setup, the compiler would generate a separate piece of code for each size value N! That's potentially a lot of code, and all that N provides is a bound in a loop.

So the suggestion here is to turn compile-time into runtime parameters and refactor the workload into a separate function, and only use template stubs to dispatch the call:

template <typename T>
void multiply_impl(unsigned int N,
                   MatrixBuf<T> const & in1, MatrixBuf<T> const & in1,
                   MatrixBuf<T> & out)
{
   // heavy work
}

template <typename T, unsigned int N>
Matrix<T, N> multiply(Matrix<T, N> const & in1, Matrix<T, N> const & in1)
{
  Matrix<T, N> out;
  multiply_impl(N, in1.buf(), in2.buf(), out.buf());
}

You could do something similar: Put all the argument-independent code in a base class, and make the derived classes templates. The runtime can then use a factory function to create the correct concrete instance at runtime. As an alternative to inheritance you can also make a type-erasing wrapper class that contains a private pointer-to-base, and the runtime populates this with concrete derived implementation instances.

Upvotes: 1

Related Questions