Vennor
Vennor

Reputation: 656

Runtime mapping of values to types

I have no hope that what I'd like to achieve is possible in C++, but maybe I'm wrong since my previous question about bidirectional static mapping got an unlikely answer.

I have a set of certain types, an enumeration with keys representing the types, and a template handle type that accepts mentioned types as template parameters.

struct foo {};
struct bar {};

enum class types { foo, bar };

template<typename T>
struct qux {};

I'd like to be able to map types::foo to foo at runtime. Most of the time foo would be used as the template parameter of qux, so mapping of types::foo to qux<foo> is fine too but I feel that if one is possible, then the other is too.

I'm mentioning this because it's important to note that qux is a simple handle-like type that only consists of an index and is passed around by value and there are a lot of template functions that take qux<T> as a parameter.

This makes polymorphism - a standard solution in such cases - not an obvious choice.

Sometimes though I need to create a qux<T> while having only a variable holding a types value, so it has to be mapped to the proper type somehow.

What I've been doing up until now is just switching each time I have to do this but I hit the point where there's too many switches to maintain.

I don't see a better solution, so what I'm looking to do is create a single swich or other mechanism in the code that will take types value and return... something that will let me create a qux<T> with related type.

Ultimately it'd work like this.

template<typename T>
void baz(qux<T> q) { /* ... */ }

// Somewhere else...
types t = get_type(); // Read type at runtime.
baz(create_object(t)); // Calls adequate baz specialization based on what value t contains.

I don't know how to implement the create_object function though.


What I tried already:

  1. std::variant with careful use of emplace and index - quickly hit the problem of being unable to return different types from a single function;
  2. clever use of conversion operators - doesn't allow me to call a proper templated function taking qux<T> as a parameter since it's not decided which specialization should be called;
  3. external polymorphism - unable to return different types;
  4. modified template specialization loop proposed in this answer that looked for proper types value and returned mapped type - this failed due to being unable to return different types - or called a lambda with auto parameter - which also failed as it tried to specialize the lambda multiple times.

Upvotes: 2

Views: 345

Answers (1)

HTNW
HTNW

Reputation: 29148

std::visit is your friend here. Convert types to a certain std::variant/replace it with an alias to that type:

// or std::type_identity
template<typename T> struct proxy { using type = T };
template<typename T> constexpr inline proxy<T> proxy_v{};
using var_types = std::variant<proxy<foo>, proxy<bar>>;
var_types mk_var_types(types t) {
    switch(t) {
        case types::foo: return proxy_v<foo>;
        case types::bar: return proxy_v<bar>;
    }
}

/write a custom std::visit-like for types (all three choices are equivalent, but replacing types is the shortest)

template<typename F>
decltype(auto) visit(F &&f, types t) {
   switch(t) {
       case types::foo: return std::forward<F>(f)(proxy_v<foo>);
       case types::bar: return std::forward<F>(f)(proxy_v<bar>);
   }
}

This can be used to implement a std::variant-of-quxs-returning create_object

auto create_object(var_types t) {
    std::visit([](auto p) -> std::variant<qux<foo>, qux<bar>> { return qux<typename decltype(p)::type>{} };, t);
}
// or
auto create_object(types t) {
    return create_object(mk_var_types(t));
}
// or
auto create_object(types t) {
     return visit([](auto p) -> std::variant<qux<foo>, qux<bar>> { return qux<typename decltype(p)::type>{}; }, t);
}

Which can be used to call baz

types t;
// or
var_types t;
std::visit([](auto &&q) { baz(std::forward<decltype(q)>(q)); }, create_object(t));

Of course, create_object isn't necessary in this case

visit([](auto p) { baz(qux<typename decltype(p)::type>{}); }, /*mk_var_types(*/t/*)*/);

Repeating foo and bar everywhere is itself a pain. This can be rectified:

template<template<typename> typename F>
using variant_with = std::variant<F<foo>, F<bar>>;
using var_types = variant_with<proxy>;
using a_qux = variant_with<qux>;
a_qux create_object(a_type); // etc.

Upvotes: 1

Related Questions