Home > Software design >  Elegantly switching template arguments for a set of functions
Elegantly switching template arguments for a set of functions

Time:07-28

I am writing some code that uses an external library, where several functions are defined approximately like this:

// Library.h

template<typename T>
void foo(int arg1, bool arg2);

template<typename T>
int bar(float arg);

(examples are given to illustrate that both argument lists and return value types are diverse, but do not contain the template type T).

In my code, I want to be able to call different template instances offoo and bar, depending on some internal mapping logic. This can be e.g. a mapping from an enum representing data types, but, importantly, this logic is the same for foo, bar, or anything else form this library.

A simple way to achieve this would be something like

// MyCode.h

enum class MyType { BOOL, CHAR };

void foo_wrapper(MyType type, int arg1, bool arg2)
{
  if (type == MyType::BOOL)
    return foo<bool>(arg1, arg2);
  else if (type == MyType::CHAR)
    return foo<char>(arg1, arg2);
  else
    throw std::runtime_error("oops");
}

int bar_wrapper(MyType type, float arg)
{
  if (type == MyType::BOOL)
    return bar<bool>(arg);
  else if (type == MyType::CHAR)
    return bar<char>(arg);
  else
    throw std::runtime_error("oops");
}

However, this is a lot of logic duplication and correcting the arg names, etc., when it would be needed for another function, leaving plenty of possibilities for missing something. My current solution is to have a static map of relevant template instantiations in each wrapper function:

void foo_wrapper(MyType type, int arg1, bool arg2)
{
  using FunctionType = std::function<void(int, bool)>;
  static const std::unordered_map<MyType, FunctionType> functionMap{
    {BOOL, foo<bool>}, 
    {CHAR, foo<char>}
  };
  if (!functionMap.count(type))
    throw std::runtime_error("oops");
  return functionMap.at(type)(arg1, arg2);
}

int bar_wrapper(MyType type, float arg)
{
  using FunctionType = std::function<int(float)>;
  static const std::unordered_map<MyType, FunctionType> functionMap{
    {BOOL, bar<bool>}, 
    {CHAR, bar<char>}
  };
  if (!functionMap.count(type))
    throw std::runtime_error("oops");
  return functionMap.at(type)(arg);
}

Upside: Arguments are passed only in one place in code, the mapping is "centralized" at the beginning of each wrapper instead of distributed in wrapper function code. Also, less code of the choice logic is being copied around.

But: We still need to duplicate the mapping correspondencies - now in the shape of a map declaration - across multiple wrappers (imagine a dozen library functions used in this way...).

Ideally, I would like to have a magic switch_type_for_func implemented that would allow doing something like

void foo_wrapper(MyType type, int arg1, bool arg2)
{
  return switch_type_for_func<foo>(type, arg1, arg2);
}

int bar_wrapper(MyType type, float arg)
{
  return switch_type_for_func<bar>(type, arg);
}

I see that this cannot work because foo is a template, but it intuitively feels as if there should be some solution that would eliminate code duplication in this case.

I can almost imagine a macros doing the job (because what I need is just the name of the function, not much more), but AFAIU these are not exactly best practice... Maybe I am just stuck in my way of thinking about it and there is something more appropriate. Any feedback/advice is appreciated!

CodePudding user response:

Map your types to integrals, and select the correct one at run-time.

enum class MyType { 
    BOOL = 0,
    CHAR,
    RESERVED,
};

using UnderlytingT = std::underlying_type_t<MyType>;

template <UnderlytingT Type>
bool foo_helper(MyType type, int arg1, bool arg2) {
    if (static_cast<UnderlytingT>(type) == Type) {
        if constexpr (Type == static_cast<UnderlytingT>(MyType::BOOL)) {
            foo<bool>(arg1, arg2);
        } else if (Type == static_cast<UnderlytingT>(MyType::CHAR)) {
            foo<char>(arg1, arg2);
        }
        return true;
    }
    return false;
}

template <UnderlytingT... Types>
bool select_foo(MyType type, std::integer_sequence<UnderlytingT, Types...>, int arg1, bool arg2) {
    return (foo_helper<Types>(type, arg1, arg2) || ...);
}

void foo_wrapper(MyType type, int arg1, bool arg2)
{
    constexpr static UnderlytingT enum_size = static_cast<UnderlytingT>(MyType::RESERVED);
    select_foo(type, std::make_integer_sequence<UnderlytingT, enum_size>{}, arg1, arg2);
}

Demo

CodePudding user response:

Ok, thanks to the comment from @463035818_is_not_a_number about template template parameters and the thread here, I came to sort of a solution that technically achieved the goal of not duplicating the choice logic, but requires something that feels like too much wrapper code to make it work (basically a trivial struct to wrap each needed library function) - see full code here:

template<class T>
struct Foo
{
    static auto run(int arg1, bool arg2)
    {
        return foo<T>(arg1, arg2);
    }
};

template<class T>
struct Bar
{
    static auto run(float arg)
    {
        return bar<T>(arg);
    }
};

template<template <typename> class T, typename ... Args>
auto switch_type_for_func(MyType type, Args... args)
{
    if (type == MyType::BOOL)
      return T<bool>::run(args...);
    else if (type == MyType::CHAR)
      return T<char>::run(args...);
    else
      throw std::runtime_error("oops");
}

void foo_wrapper(MyType type, int arg1, bool arg2)
{
  return switch_type_for_func<Foo>(type, arg1, arg2);
}

int bar_wrapper(MyType type, float arg)
{
  return switch_type_for_func<Bar>(type, arg);
}

Slightly simplifying this, we could call switch_type_for_func something more pretty and get rid of the foo_wrapper and bar_wrapper, directly calling in the main code e.g. lib_helper<Bar>(type, arg); (see full code here).

If anyone sees a way to avoid the ugly structs, please add your ideas!

  • Related