【问题标题】:Combination explosion of an enum value (729 combinations...)枚举值的组合爆炸(729 个组合...)
【发布时间】:2019-03-04 17:23:13
【问题描述】:

我面临一个问题,我必须生成大量代码,所有代码都非常相似,我想知道是否有任何方法可以模板化。

假设我有这种类型的结构

template <typename ...NodeKindTs>
struct Element{
std::tuple<NodeKindTs...> nodes;
}

我有一个整数向量,它将一个节点与另一个节点相关联,还有一个枚举向量,它表示每个节点是哪种类型。种类可以是 A、B 或 C。

enum class Kind {A,B,C};
std::vector<int> relationShip;
std::vector<Kind> kind;

例如,如果我有

relationShip = {1,2,-1};
kind = {A,B,A}

表示第一个节点属于 A 类,并且与属于 B 类的第二个节点相关。你明白了。

现在,我必须创建元素并将它们插入到向量中,具体取决于每个节点和关系的 NodeKind。此元素由多达 6 个 NodeKinds 模板化。为了解决这个问题,我需要一个巨大的 if 来检查每个节点的 Kind 然后调用 Element ctor。

对于 2 NodeKinds 的情况,这意味着做类似的事情

if (node1.type == A && node2.type == A) {
auto &simNode1 = containerOfAs(node1.id);
auto &smiNode2 = containerOfAs(node2.id);
insertElement(elementFactory(simNode1, simNode2));
}
if (node1.type == A && node2.type == C) 
{
auto &simNode1 = containerOfAs(node1.id);
auto &smiNode2 = containerOfCs(node2.id);
insertElement(elementFactory(simNode1, simNode2));
}
if (node1.type == B && node2.type == C) 
{
auto &simNode1 = containerOfBs(node1.id);
auto &smiNode2 = containerOfCs(node2.id);
insertElement(elementFactory(simNode1, simNode2));
}
...

inserElement 是我创建的一个元函数,如果适合从容器列表中插入元素,它会将元素插入容器中。

对于这 2 种情况,这最多需要 9 个 if。对于第 3 种情况,它需要 27 个 ifs,而对于第 6 种情况,它需要 729 个 ifs。我真的不想对它们进行编码。

知道如何解决这个问题吗?

谢谢!

【问题讨论】:

  • 听起来有点像在 Boost Units 中解决类似问题的方式。 boost.org/doc/libs/1_69_0/doc/html/boost_units.html
  • 您仍然可以使用std::variant 让它为您完成组合...但我担心它会减慢编译速度。
  • @Jarod42 你将如何使用 std::variant 来解决这个问题。我一直在思考,但没有找到答案。
  • @Eljay 我一直在查看 Boost.Units 的文档,但我没有发现类似的问题。你能多指点我吗?谢谢!
  • Boost Units 对 SI 单位的各种排列使用参数化模板。也许我误解了您面临的问题。

标签: c++ enums combinations metaprogramming


【解决方案1】:

使用std::variant,您可能会得到类似的结果:

std::variant<std::reference_wrapper<NodeA>,
             std::reference_wrapper<NodeB>,
             std::reference_wrapper<NodeC>> getNodeAsVariant(Kind kind, int id)
{
    switch (kind) {
        case Kind::A: return containerOfAs(id);
        case Kind::B: return containerOfBs(id);
        case Kind::C: return containerOfCs(id);
    }
    throw std::runtime_error("Invalid kind");
}

然后

auto v1 = getNodeAsVariant(node1.type, node1.id);
auto v2 = getNodeAsVariant(node2.type, node2.id);
auto v3 = getNodeAsVariant(node3.type, node3.id);
auto v4 = getNodeAsVariant(node4.type, node4.id);
auto v5 = getNodeAsVariant(node5.type, node5.id);
auto v6 = getNodeAsVariant(node6.type, node6.id);

// r1, .., r6 would be the correct reference_wrapper<T>
std::visit([](auto r1, auto r2, auto r3, auto r4, auto r5, auto r6) {
         insertElement(elementFactory(r1/*.get()*/, r2, r3, r4, r5, r6));
    }, v1, v2, v3, v4, v5, v6);

所以std::visit 将为您生成 729 重载。

【讨论】:

  • 元素工厂接收对节点的引用(在本例中为reference_wrapper)。变体是否隐式转换为其基础类型?它怎么知道它必须转换为类型 NodeA 或类型 NodeB?
  • std::visit 具有魔力,根据v1.index(),调用与f(std.get&lt;v1.index()&gt;(v1), std.get&lt;v2.index()&gt;(v2)) 对应的正确重载(std.get&lt;v1.index()&gt;(v1) 无效,因为v1.index() 是运行时,但我希望你明白)。
  • 很好。我想过这样的解决方案,但我不确定它是否有效,因为我认为您需要在编译时知道重载。我现在用 2 型外壳测试它。你知道它是否适用于 Boost.Variant?谢谢!
  • boost::variant 具有boost:: apply_visitor。(但您似乎需要从boost::static_visitor 继承,因此创建专用结构而不是 lambda)。
  • 还是不行。我创建了访问者 lambda 并传递了 2 类型案例的两个变体。我收到一个编译器错误,说工厂接收变体没有过载。我的工厂是一个模板,它接收 T1 和 T2 的两个引用(对于 2 类型案例)并返回 Element.
【解决方案2】:

也许这样的东西可以作为一个起点(我可能误解了这个问题,可能有办法让它更短):

#include <iostream>

enum class Kind { A, B, C };

std::ostream& operator<<(std::ostream& os, Kind k) {
    switch (k) {
        case Kind::A: return os << "A";
        case Kind::B: return os << "B";
        case Kind::C: return os << "C";
    }
    return os << "Unknown";
}


template<typename F>
void dispatch(Kind k, F f) {
    switch (k) {
        case Kind::A: f.template call<Kind::A>(); return;
        case Kind::B: f.template call<Kind::B>(); return;
        case Kind::C: f.template call<Kind::C>(); return;
    }
    abort();
}

template<Kind k1>
struct handle2 {
    template<Kind k2>
    void call() {
        std::cout << "k1=" << k1 << " k2=" << k2 << "\n";
        // Do your thing with k1 and k2 here
    }
};

struct handle1 {
    Kind k2;
    template<Kind k1>
    void call() {
        dispatch(k2, handle2<k1>{});
    }
};

void handle(Kind k1, Kind k2) {
    dispatch(k1, handle1{k2});
}

int main() {
    handle(Kind::C, Kind::B);
}

【讨论】:

  • 我看不出这是如何解决问题的。您的调度函数需要两个枚举值。正如我所说,我最多可以有 6 个值。另外,您不使用 NodeKind 做任何事情,那么如何创建 Element 类型?
  • 好吧,就像我写的那样,我可能误解了你的问题,所以我只勾勒出我认为可以解决 2 个值的问题。我仍然不是 100% 清楚你需要什么,但我会在 handle2.call 中创建元素(可能会一路传递对所需上下文的引用)。如果我完全不在基地,请告诉我,我会删除答案。
  • 如果我有 Enum1、Enum1、Enum1 组合,我需要创建一个 Element,如果我有 Enum2、Enum1、Enum2 等,我需要创建 Element .对于两种类型很容易,它的唯一组合(给定 3 个枚举值)。对于更多类型,它变得太多输入,我正在寻找一种自动生成元素的方法。您能否尝试描述一个 3 类型系统,看看我是否能更好地理解如何扩展它?谢谢!
猜你喜欢
  • 1970-01-01
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
  • 2018-02-14
  • 1970-01-01
  • 2020-05-20
  • 2012-12-21
相关资源
最近更新 更多