Reputation: 656
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 switch
ing each time I have to do this but I hit the point where there's too many switch
es 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:
std::variant
with careful use of emplace
and index
- quickly hit the problem of being unable to return different types from a single function;qux<T>
as a parameter since it's not decided which specialization should be called;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
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-qux
s-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