Intro

In my codebases, there’re lots of situations where different operations are dispatched based on data types that are undetermined until runtime. So one problem here is how to do the mapping. Consider the following code snippet,

template<typename T>
bool foo(const int& v) {
    std::cout << __PRETTY_FUNCTION__ << " " << v << "\n";
    return sizeof(T) > 4;
}

foo is a function template whose template parameter can’t be deduced here. It’s just a showcase and doesn’t have specific meanings. Now, we want to call it of types according to a value from the runtime, networking, or files. Say I use enums here,

enum class DataType: uint8_t { 
    kInt32,
    kDouble,
    kString
};

The enumerator names represent types being mapped to.

Basic Ideas

When I want to dispatch it, I might do this,

auto foo_wrapper(DataType type, int& arg) {
    switch (type) {
        case DataType::kInt32:
            return foo<int32_t>(arg);
        case DataType::kDouble:
            return foo<double>(arg);
        case DataType::kString:
            return foo<std::string>(arg);
        default:
            return false;
    }
}

This may be the roughest idea at the first glance. However, whenever I have a function template, I have to duplicate this bunch of code, which is unacceptable…

Is there any way to extract this code to a more general case? You may say template templates and pass function templates. Unfortunately, it’s not allowed.

But wrapping a function into a struct and passing it as a template template argument is valid. Yes, LAMBDAs!

Lambda

template <typename F>
auto dispatcher(F&& f, DataType t) {
    switch (t) {
        case DataType::kInt32:
            return f(int32_t{});
        case DataType::kDouble:
            return f(double{});
        case DataType::kString:
            return f(std::string{});
        default:
            return false;
    };
}

auto foo_wrapper(DataType type, int& arg) {
    return dispatcher([&](auto t) { return foo<decltype(t)>(arg); }, type);
}

The main idea is to wrap the function template into a generic lambda, whose parameter type is the mapped one. In the dispatching part, we construct a default value of its type so that it can be deduced. Godbolt

templated lambda introduced in C++20 will make it neater. You don’t have to pass something to be deduced.

template <typename F>
auto dispatcher(F&& f, DataType t) {
    switch (t) {
        case DataType::kInt32:
            return f.template operator()<int32_t>();
        case DataType::kDouble:
            return f.template operator()<double>();
        case DataType::kString:
            return f.template operator()<std::string>();
        default:
            return false;
    };
}

auto foo_wrapper(DataType type, int& arg) {
    return dispatcher([&]<typename T>() { return foo<T>(arg); }, type);
}

GodBolt

std::visit

In C++17, std::visit already provides a way for you to dispatch. See the following solution,

using VType = std::variant<std::type_identity<int32_t>,
                           std::type_identity<double>,
                           std::type_identity<std::string>>;

static const std::unordered_map<DataType, VType> dispatcher = {
    {DataType::kInt32, std::type_identity<int32_t>{}},
    {DataType::kDouble, std::type_identity<double>{}},
    {DataType::kString, std::type_identity<std::string>{}}
};


auto foo_wrapper(DataType type, int arg1)
{
    return std::visit([&](auto v){
        return foo<typename decltype(v)::type>(arg1);
    }, dispatcher.at(type));
}

Godbolt

std::type_identity is introduced in C++20, but you can implement one by yourself. I believe this solution is a bit more elegant because it avoids the branch selection code.

However, the overhead of std::visit is non-negligent here. If you care about runtime performance, you’d better take care.

Summary

In this post, I provide a few “elegant” ways to map runtime values to types. Since the boilerplate code is necessary, I can’t say it’s 100% elegant. If you also have some good solutions or suggestions, feel free to leave a comment or contact me.