3

My question here is similar to this post expect that I have more than one template argument and string. Thus, the setup is

class base_class; // no template args

template<typename T, typename U, typename V>
class child_class : public base_class;

I have a limited number of implemented types for T, U and V which I want to select at runtime given three strings. So as the question in cited post, I could do something like

std::unique_ptr<base_class> choose_arg1(
    std::string T_str, std::string U_str, std::string v_str){
  if(T_str == "int"){
    return(choose_arg2<int>(U_str, V_str));

  } else if(T_str == "char"){
    return(choose_arg2<char>(U_str, V_str));

  } // ...
}

template<typename T>
std::unique_ptr<base_class> choose_arg2(std::string U_str, std::string v_str){
  if(U_str == "int"){
    return(choose_arg3<T, int>(V_str));

  } else if(U_str == "char"){
    return(choose_arg3<T, char>(V_str));

  } // ...
}

template<typename T, typename U>
std::unique_ptr<base_class> choose_arg3(std::string v_str){
  if(v_str == "int"){
    return(std::make_unique<child_class<T, U, int>>());

  } else if(v_str == "char"){
    return(std::make_unique<child_class<T, U, char>>());

  } // ...
}

but is there a better way? I have less than 5^3 combination for the record.

5
  • 2
    Should this be portable or are you just playing around with your computer and a particular compiler (GCC...)? Commented Oct 24, 2017 at 13:51
  • 1
    It should be portable. Commented Oct 24, 2017 at 13:51
  • You could have a static std::map<std::string, std::function<std::unique_ptr<base_class>(std::string const &)>> in each function and then search for the argument string in it. Commented Oct 24, 2017 at 14:58
  • @Darhuuk so like this answer in the post I link to in my question? Commented Oct 24, 2017 at 15:15
  • @BenjaminChristoffersen Ah yes. Seems like I wasted my time typing up an example for your code :). Commented Oct 24, 2017 at 15:15

3 Answers 3

3

I suggest to develop a template helper struct with a couple of static func() methods

template <typename ... Ts>
struct choose_args_h
 {
   using retT = std::unique_ptr<base_class>;

   template <typename ... Args>
   static retT func (std::string const & s, Args const & ... args)
    {
      if ( s == "int" )
         return choose_args_h<Ts..., int>::func(args...);
      else if ( s == "char" )
         return choose_args_h<Ts..., char>::func(args...);
      // else ...
    }

   static retT func ()
    { return std::make_unique<child_class<Ts...>>(); }
 };

so you can write a choose_args() func simply as follows

template <typename ... Args>
std::unique_ptr<base_class> choose_args (Args const & ... args)
 { return choose_args_h<>::func(args...); }

The following is a full working example

#include <string>
#include <memory>

class base_class
 { };

template <typename, typename, typename>
class child_class : public base_class
 { };

template <typename ... Ts>
struct choose_args_h
 {
   using retT = std::unique_ptr<base_class>;

   template <typename ... Args>
   static retT func (std::string const & s, Args const & ... args)
    {
      if ( s == "int" )
         return choose_args_h<Ts..., int>::func(args...);
      else if ( s == "char" )
         return choose_args_h<Ts..., char>::func(args...);
      // else ...
    }

   static retT func ()
    { return std::make_unique<child_class<Ts...>>(); }
 };

template <typename ... Args>
std::unique_ptr<base_class> choose_args (Args const & ... args)
 { return choose_args_h<>::func(args...); }

int main ()
 {
   auto p0 = choose_args("int", "char", "int");
   auto p1 = choose_args("int", "char", "char");
 }
Sign up to request clarification or add additional context in comments.

4 Comments

Unless there's specific restrictions on which type is allowed for each template argument, I consider this the much nicer version of what I suggested :).
Yes, unfortunately I do have restrictions but I agree.
@BenjaminChristoffersen - sorry: from your example I've understood that there is the same list of string argument and the same choose of templates in every position; if it isn't, I suspect that your example -- a function for every level -- it's a good solution; not necessarily the best but a good one.
@max66 I Agree, my example was not clear in that regard. I should have been more specific.
2

Shown in this post is a C++17 solution with compile-time configuration of the allowed types and corresponding keys via the type Argmaps. The lookup is done by a compile-time loop.

C++11 does not support generic lambdas which are required for the compile-time loops used here. Instead, one could perform the lookup by template meta-programming with the "indices trick" (as in this online demo), but that feels too complicated and I prefer the std::map approach anyway. Note that my linked C++11 attempt could call the constructor twice if the keys are not unique.

#include <iostream>
#include <memory>
#include <string>

#include "loop.hpp"

template<class... Ts> struct Types {
  static constexpr size_t size = sizeof...(Ts);

  template<size_t i>
  using At = std::tuple_element_t<i, std::tuple<Ts...>>;
};

template<class... Ts> constexpr Types<Ts...> to_types(Ts...) { return {}; }

template<auto... cs> struct Str {
  operator std::string() const {
    constexpr auto list = std::initializer_list<char>{cs...};
    return std::string{list.begin(), list.end()};
  }
};

template<class Char, Char... cs>
constexpr auto operator""_c() {
  return Str<cs...>{};
}

//////////////////////////////////////////////////////////////////////////////

struct Base {
  virtual void identify() const = 0;
};

template<class... Ts>
struct Derived : Base {
  virtual void identify() const override {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

using Ptr = std::unique_ptr<Base>;

//////////////////////////////////////////////////////////////////////////////

template<class Argmaps, class Args=Types<>>
struct choose_impl;

template<class Map0, class... Maps, class... Args>
struct choose_impl<Types<Map0, Maps...>, Types<Args...>> {
  static constexpr size_t pos = sizeof...(Args);

  template<class S0, class... Ss>
  static Ptr get(S0 s0, Ss... ss) {
    Ptr ret{nullptr};

    using namespace Loop;
    loop(less<Map0::size>, [&] (auto i) {
      using Argmapping = typename Map0::template At<i>;
      using Key = typename Argmapping::template At<0>;
      using Arg = typename Argmapping::template At<1>;
      using Recursion = choose_impl<Types<Maps...>, Types<Args..., Arg>>;
      if(std::string(Key{}) == s0) ret = Recursion::get(ss...);
    });

    if(!ret) {
      std::cerr << "NOT MAPPED AT POS " << pos << ": " << s0 << std::endl;
      std::terminate();
    }

    return ret;
  }
};

template<class... Args>// all Args are resolved
struct choose_impl<Types<>, Types<Args...>> {
  static Ptr get() {
    return std::make_unique<Derived<Args...>>();
  }
};

template<class Argmaps, class... Ss>
Ptr choose(Ss... ss) {
  static_assert(Argmaps::size == sizeof...(Ss));
  return choose_impl<Argmaps>::get(std::string(ss)...);
}

template<class V, class K>
auto make_argmapping(K) {
  return Types<K, V>{};
}

//////////////////////////////////////////////////////////////////////////////

int main() {
  using Argmaps = decltype(
    to_types(
      to_types(// first template parameter
        make_argmapping<int>("int"_c),
        make_argmapping<char>("char"_c),
        make_argmapping<bool>("bool"_c)
      ),
      to_types(// ... second ...
        make_argmapping<double>("double"_c),
        make_argmapping<long>("long"_c)
      ),
      to_types(// ... third
        make_argmapping<bool>("bool"_c)
      )
    )
  );

  choose<Argmaps>("int", "double", "bool")->identify();
  choose<Argmaps>("int", "long", "bool")->identify();
  choose<Argmaps>("char", "double", "bool")->identify();
  choose<Argmaps>("char", "long", "bool")->identify();
  choose<Argmaps>("bool", "double", "bool")->identify();
  choose<Argmaps>("bool", "long", "bool")->identify();

// bad choice:
  choose<Argmaps>("int", "int", "bool")->identify();

  return 0;
}

loop.hpp from this unread answer:

#ifndef LOOP_HPP
#define LOOP_HPP

namespace Loop {

template<auto v> using Val = std::integral_constant<decltype(v), v>;

template<auto i> struct From : Val<i> {};
template<auto i> static constexpr From<i> from{};

template<auto i> struct Less : Val<i> {};
template<auto i> static constexpr Less<i> less{};

// `to<i>` implies `less<i+1>`
template<auto i> struct To : Less<i+decltype(i)(1)> {};
template<auto i> static constexpr To<i> to{};

template<auto i> struct By : Val<i> {};
template<auto i> static constexpr By<i> by{};

template<auto i, auto N, auto delta, class F>
constexpr void loop(From<i>, Less<N>, By<delta>, F f) noexcept {
  if constexpr(i<N) {
    f(Val<i>{});
    loop(from<i+delta>, less<N>, by<delta>, f);
  }
}

// overload with two arguments (defaulting `by<1>`)
template<auto i, auto N, class F>
constexpr void loop(From<i>, Less<N>, F f) noexcept {
  loop(from<i>, less<N>, by<decltype(i)(1)>, f);
}

// overload with two arguments (defaulting `from<0>`)
template<auto N, auto delta, class F>
constexpr void loop(Less<N>, By<delta>, F f) noexcept {
  loop(from<decltype(N)(0)>, less<N>, by<delta>, f);
}

// overload with one argument (defaulting `from<0>`, `by<1>`)
template<auto N, class F>
constexpr void loop(Less<N>, F f) noexcept {
  using Ind = decltype(N);
  loop(from<Ind(0)>, less<N>, by<Ind(1)>, f);
}

} // namespace Loop

#endif

http://coliru.stacked-crooked.com/a/5ce61617497c3bbe

3 Comments

Cool, I will have a look at this later.
Looks neat. Though, I need it the code to be able to build with compilers that only has support for C++11. Will that be possible?
See my edit where I linked an incomplete C++11 attempt.
2

As I noted in my comment, you could use a static map of string to function.

For your example code (slightly simplified to 2 template parameters to make it a little shorter), this would become:

#include <iostream>
#include <string>
#include <map>
#include <functional>
#include <memory>

class base_class { }; // no template args

template<typename T, typename U>
class child_class : public base_class { };

using ptr_type = std::unique_ptr<base_class>;

// Declarations
std::unique_ptr<base_class> choose_arg1 (std::string const & T_str,
    std::string const & U_str);

template<typename T>
std::unique_ptr<base_class> choose_arg2 (std::string const & U_str);

// Definitions
std::unique_ptr<base_class> choose_arg1 (std::string const & T_str,
    std::string const & U_str) {
  using function_type = std::function<ptr_type(std::string const &)>;
  using map_type = std::map<std::string, function_type>;

  static const map_type ptrMap = {
    {"int",  choose_arg2<int>  },
    {"char", choose_arg2<char> }
  };

  auto ptrIter = ptrMap.find(T_str);
  return (ptrIter != ptrMap.end()) ? ptrIter->second(U_str) : nullptr;
}

template<typename T>
std::unique_ptr<base_class> choose_arg2 (std::string const & U_str) {
  using function_type = std::function<ptr_type()>;
  using map_type = std::map<std::string, function_type>;

  static const map_type ptrMap = {
    {"int",  []{ return std::make_unique<child_class<T, int>>();  } },
    {"char", []{ return std::make_unique<child_class<T, char>>(); } }
  };

  auto ptrIter = ptrMap.find(U_str);
  return (ptrIter != ptrMap.end()) ? ptrIter->second() : nullptr;
}

int main () {
  std::cout << typeid(choose_arg1("int", "char")).name() << "\n";
  std::cout << "[Done]\n";
}

5 Comments

Very nice. Short meta question: Is it good practice to delete my own answer if I prefer yours?
@Julius Thanks! I'd leave your answer. I'd like to take a look at it later tonight :).
It is not clear to me what I win with the approach rather than if-else if approach I suggest?
This approach with an std::map looks very powerful to me. I guess it can be applied to create a configurable/variadic version like mine, but without those ugly types and more readable.
@BenjaminChristoffersen To me the advantage versus the if-else approach is that you don't have to repeat the if-statement all the time and have all possible choices clearly in 1 location (the constructor of the static map). In other words, I find it much more readable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.