Search code examples
c++templatesvariadic-templates

Elegantly switching template arguments for a set of functions


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!


Solution

  • First of all, it's clear that there is no 100% elegant solution because you can't really do anything with function templates except instantiating them. You can't abstract over them whatsoever. So at least a small amount of boilerplate code for every function template is required.

    But I think lambdas provide a neat way to accomplish this with the least amount of typing overhead possible. In C++20, you can do this:

    template <class F>
    auto dispatcher(F&& f, MyType t) {
        switch (t) {
            case MyType::BOOL:
                return f.template operator()<bool>();
            case MyType::CHAR:
                return f.template operator()<char>();
        };
    }
    
    auto foo_wrapper(MyType type, int arg1, bool arg2) {
        return dispatcher([&]<class T>() { return foo<T>(arg1, arg2); }, type);
    }
    
    auto bar_wrapper(MyType type, float arg) {
        return dispatcher([&]<class T>() { return bar<T>(arg); }, type);
    }
    

    https://godbolt.org/z/796xETr3o

    That's two lines per new MyType value, and one lambda (inside the wrapper function you were going to write anyway) per function you want to wrap. I don't think you'll get much better than this.

    The default by-reference capture should be perfectly fine - it would only fail if you were to take something by value in the wrapper which the original function takes by ref (and even then it would have to store the reference in global state to cause danger), which you obviously shouldn't do.

    FWIW, you can emulate this before C++20 (but with C++14) using polymorphic lambdas instead, it's just more ugly (if you don't want to go all crude):

            // ...
            case MyType::BOOL:
                return f(std::type_identity<bool>{});
            // ...
            return dispatcher([&](auto ti) { return foo<decltype(ti)::T>(arg1, arg2); }, type);
    

    https://godbolt.org/z/7cjdzx681

    Quite a bit more awkward to type/decipher, but doesn't require templated lambdas.