【问题标题】:Runtime mapping of values to types值到类型的运行时映射
【发布时间】:2020-07-06 15:45:38
【问题描述】:

我不希望在 C++ 中实现我想要实现的目标,但也许我错了,因为我之前关于 bidirectional static mapping 的问题得到了一个不太可能的答案。

我有一组特定类型,一个带有表示类型的键的枚举,以及一​​个接受提到的类型作为模板参数的模板句柄类型。

struct foo {};
struct bar {};

enum class types { foo, bar };

template<typename T>
struct qux {};

我希望能够在运行时将types::foo 映射到foo。大多数时候foo 会被用作qux 的模板参数,所以types::fooqux&lt;foo&gt; 的映射也很好,但我觉得如果一个可能,那么另一个也是。

我之所以提到这一点是因为重要的是要注意qux 是一个简单的类似句柄的类型,它只包含一个索引并按值传递,并且有很多模板函数将qux&lt;T&gt; 作为一个参数。

这使得多态性——这种情况下的标准解决方案——不是一个明显的选择。

虽然有时我需要创建一个qux&lt;T&gt;,而只有一个变量持有types 值,所以它必须以某种方式映射到正确的类型。

到目前为止,我一直在做的只是switching,每次我必须这样做,但我达到了需要维护太多switches 的地步。

我没有看到更好的解决方案,所以我想做的是在代码中创建一个 swich 或其他机制,它将获取 types 值并返回......这会让我创建一个具有相关类型的qux&lt;T&gt;

最终它会像这样工作。

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.

虽然我不知道如何实现create_object 函数。


我已经尝试过的:

  1. std::variant 小心使用 emplaceindex - 快速解决无法从单个函数返回不同类型的问题;
  2. clever use of conversion operators - 不允许我调用以 qux&lt;T&gt; 作为参数的正确模板函数,因为它没有决定应该调用哪个专业化;
  3. external polymorphism - 无法返回不同的类型;
  4. 修改了this answer 中提出的模板特化循环,它寻找正确的types 值并返回映射类型 - 由于无法返回不同类型而失败 - 或使用 auto 参数调用 lambda - 这也失败为它尝试多次专门化 lambda。

【问题讨论】:

  • 模板在编译时被实例化。 qux::&lt;foo&gt; 需要在编译时实例化,如果映射发生在运行时就不会发生这种情况。
  • 如果你走这条路,你在这里真正能做的最好的事情是执行某种类型擦除,这样你就有一个类型擦除的类(如void*),它从一些@返回987654352@ 函数接收一些输入,例如来自 type_info 结构的 hash_code,并正确映射到返回 qux&lt;foo&gt; 类型擦除为 void* 的函数,然后调用者必须执行 static_cast恢复类型。
  • std::variant 似乎正是这里的正确工具。你尝试了什么?在您的示例中,create_object 应该返回 std::variant&lt;qux&lt;foo&gt;, qux&lt;bar&gt;&gt; 并且您将调用 bazstd::visit([&amp;](auto &amp;&amp;x) -&gt; decltype(auto) { return baz(std::forward&lt;decltype(x)&gt;(x)); }, create_object(t)) (是的,咒语很长,但是要完成这项工作还有很多工作要做)。哎呀,std::variant 工作得很好,它可以直接替换 types:using types = std::variant&lt;std::type_identity&lt;foo&gt;, std::type_identity&lt;bar&gt;&gt;,这可能有助于清理事情(或者可能不会,idk)。
  • @Vennor 我已经去那里简化了我的答案。我不知道为什么我把它弄得这么复杂。如果您在types 中实现了std::visit,那么您基本上已经将types 变成了std::variantstd::variant 基本上 std::visit)。我的建议让create_object 返回std::variant 只是这个想法的一种变体。

标签: c++


【解决方案1】:

std::visit 是你的朋友。将types 转换为某个std::variant/将其替换为该类型的别名:

// 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>;
    }
}

/为types写一个自定义的std::visit-like(三个选项都是等价的,但是替换types是最短的)

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>);
   }
}

这可以用来实现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);
}

可用于拨打baz

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

当然,create_object 在这种情况下是不必要的

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

到处重复foobar 本身就是一种痛苦。这可以纠正:

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.

【讨论】:

    猜你喜欢
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    • 2019-04-09
    • 2020-07-21
    • 2021-06-01
    • 2019-08-17
    • 1970-01-01
    相关资源
    最近更新 更多