Peter
Peter

Reputation: 3155

Avoiding code duplication for template instantiations depending on runtime parameters

I'm working with a C++ library written by someone else which (unfortunately imo) implements several algorithms encapsulated in classes whose behaviour can be fine tuned via parameters that are passed as template parameters (the parameters are generally also classes). So for example there might be a class template like this:

template<typename Param1, typename Param2, typename Param3>
class Foo {
  // ...
};

I want to write a program using this library that will need to create instances of Foo based on runtime information. My problem is that assuming that there are N1, N2 and N3 valid types that can be passed for Param1, Param2 and Param3, I might need to create up to N1 x N2 x N3 branches at some point in my code when I want to instantiate different spezializations of Foo. Say for example that I at runtime I am given three strings via user input and each of those decides which type one of the template parameters of Foo should be, then I would need to do something like this:

std::string s1, s2, s3;

// instantiate s1, s2, s3 from user input

if (s1 == "Param1_1" && s2 == "Param2_1" && s3 == "Param3_1") {
  Foo<Param1_1, Param2_1, Param3_1> foo;
} else if (s1 == "Param1_1" && s2 == "Param2_1" && s3 == "Param3_2") {
  Foo<Param1_1, Param2_1, Param3_2> foo;
}

// ...

Where Param1_1 is a valid type for Param1 and so on. How can I implement this more elegantly? (ideally only using C++11 or at most C++17 features).

Upvotes: 0

Views: 71

Answers (2)

max66
max66

Reputation: 66200

Starting from C++11, you could create a sequence of functions, one for every type, to select and transmit the correct template parameter; the last function called (the first one, in the following example) could create the Foo object.

Not really elegant but you can avoid the N1 x N2 x N3 multiplication effect.

template <typename ... PrevParams>
void for_s3 (std::string const & s3)
 {
   if ( "Param3_1" == s3 )
      Foo<PrevParams..., Param3_1>{}
   else if ( "Param3_2" == s3 )
      Foo<PrevParams..., Param3_2>{};
   // other cases 
 }    

template <typename ... PrevParams>
void for_s2 (std::string const & s2, std::string const & s3)
 {
   if ( "Param2_1" == s2 )
      for_s3<PrevParams..., Param2_1>(s3);
   else if ( "Param2_2" == s2 )
      for_s3<PrevParams..., Param2_2>(s3);
   // other cases 
 }    

void for_s1 (std::string const & s1, std::string const & s2, std::string const & s3)
 {
   if ( "Param1_1" == s1 )
      for_s2<Param1_1>(s2, s3);
   else if ( "Param1_2" == s1 )
      for_s2<Param1_2>(s2, s3);
   // other cases 
 }

Maybe a little more elegant and flexible (you can easily add other string and parameters), but with a greater run-time cost, a two-function solution

template <typename ... Params>
void select_foo ()
 {
   Foo<Params...>  f;

   // do something with f
 }

template <typename ... PrevParams, typename ... Ts>
void select_foo (std::string const & s, Ts const & ... ts)
 {
   if ( "Param1_1" == s )
       select_foo<PrevParams..., Param1_1>(ts...);
   else ( "Param1_2" == s )
       select_foo<PrevParams..., Param1_2>(ts...);
   // other Param1_x cases
   else if ( "Param2_1" == s )
       select_foo<PrevParams..., Param2_1>(ts...);
   else ( "Param2_2" == s )
       select_foo<PrevParams..., Param2_2>(ts...);
   // other Param2_x cases
   else if ( "Param3_1" == s )
       select_foo<PrevParams..., Param3_1>(ts...);
   else ( "Param3_1" == s )
       select_foo<PrevParams..., Param3_2>(ts...);
   // other Param3_x cases
 }

Upvotes: 0

Jarod42
Jarod42

Reputation: 217275

You can use std::variant (c++17) for each type and use std::visit for the dispatch:

template <typename T> struct Tag{ using type = T;};

std::variant<Tag<T1>, Tag<T2>, Tag<T3>/*..*/> getType(const std::string& s)
{
    if (s == "Param1") { return Tag<T1>{}; }
    else if (s == "Param2") { return Tag<T2>{}; }
    // ...
}

void bar(const std::string& s1, const std::string& s2, const std::string& s3)
{
    auto v1 = getType(s1);
    auto v2 = getType(s2);
    auto v3 = getType(s3);

    std::visit([](auto t1, auto t2, auto t3)
              {
                  Foo<typename decltype(t1)::type,
                      typename decltype(t2)::type,
                      typename decltype(t3)::type> foo;
                  /*..*/
               }, v1, v2, v3);
}

Upvotes: 3

Related Questions