C++使用枚举变量调度模板

发布于 2025-02-02 13:04:44 字数 1374 浏览 3 评论 0原文

我的功能具有两个枚举变量的模板。有什么方法可以通过enumaenumb的不同组合来派遣函数f?如果,我不想写太多。在这种情况下,我可能只需要编写4个分支,但是在实际情况下,我需要编写Tens分支。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cassert>
using namespace std;

enum EnumA
{
    enumA0,
    enumA1
};
enum EnumB
{
    enumB0,
    enumB1
};

template <EnumA enumA, EnumB enumB>
void f()
{
    if (enumA == enumA0)
    {
        if (enumB == enumB0)
        {
            // something here
            return;
        }
        else if (enumB == enumB1)
        {
            // something here
            return;
        }
        else
        {
            assert(false);
        }
    }
    else if (enumA == enumA1)
    {
        if (enumB == enumB0)
        {
            // something here
            return;
        }
        else if (enumB == enumB1)
        {
            // something here
            return;
        }
        else
        {
            assert(false);
        }
    }
}

int main()
{
    int a, b;
    cin >> a >> b;
    const auto enumA = a < 0 ? enumA0 : enumA1;
    const auto enumB = b < 0 ? enumB0 : enumB1;
    f<enumA, enumB>(); // Is there a way to dispatch this with different enumA and enumB combinations?
    return 0;
}

I have a function with a template of two enum variables. Is there any way to dispatch the function f with different combinations of enumA and enumB? I do not want to write too much if. In this case, I may only need to write 4 branches, but in actual case, I need to write tens branches.

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cassert>
using namespace std;

enum EnumA
{
    enumA0,
    enumA1
};
enum EnumB
{
    enumB0,
    enumB1
};

template <EnumA enumA, EnumB enumB>
void f()
{
    if (enumA == enumA0)
    {
        if (enumB == enumB0)
        {
            // something here
            return;
        }
        else if (enumB == enumB1)
        {
            // something here
            return;
        }
        else
        {
            assert(false);
        }
    }
    else if (enumA == enumA1)
    {
        if (enumB == enumB0)
        {
            // something here
            return;
        }
        else if (enumB == enumB1)
        {
            // something here
            return;
        }
        else
        {
            assert(false);
        }
    }
}

int main()
{
    int a, b;
    cin >> a >> b;
    const auto enumA = a < 0 ? enumA0 : enumA1;
    const auto enumB = b < 0 ? enumB0 : enumB1;
    f<enumA, enumB>(); // Is there a way to dispatch this with different enumA and enumB combinations?
    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

欲拥i 2025-02-09 13:04:45

如果您的主要关注点是避免重复的,如果/else或switch/case构造,则可以在Igor指出的那样创建一个查找表。

为此,您可以将功能(您的“这里”部分)分为单个功能,然后您的查找表将其检索。

因此,我们将从您的模板功能中删除分支。现在,这只是不确定组合的全部内容。

template <EnumA enumA, EnumB enumB>
void f() {
    assert(false);
}

为了根据组合指定应该发生的事情,我们可以专业化功能模板。

template <>
void f<enumA0, enumB0>() {
    // something here
    puts("0-0");
}
template <>
void f<enumA1, enumB0>() {
    // something here
    puts("1-0");
}
template <>
void f<enumA0, enumB1>() {
    // something here
    puts("0-1");
}
template <>
void f<enumA1, enumB1>() {
    // something here
    puts("1-1");
}

定义了应该发生的事情时,我们必须桥接的唯一差距是如何“转换”运行时已知的枚举值为非类型模板参数(又称编译时已知的枚举值),我们在函数定义中使用了。换句话说,我们如何从枚举值传递到一个函数(例如dispatch(enuma1,enumb0)到我们函数专业化的调用(例如f&lt; enuma1,enumb0&gt;

void dispatch(EnumA a, EnumB b){
static const std::map<std::pair<EnumA, EnumB>, void(*)()> lookup = {
    {{enumA0, enumB0}, &f<enumA0, enumB0>},
    {{enumA0, enumB1}, &f<enumA0, enumB1>},
    {{enumA1, enumB0}, &f<enumA1, enumB0>},
    {{enumA1, enumB1}, &f<enumA1, enumB1>},
};
    return lookup.at({a, b})();
}

>
现在,您的主要内容看起来像这样:

int main()
{
    int a, b;
    cin >> a >> b;
    const auto enumA = a < 0 ? enumA0 : enumA1;
    const auto enumB = b < 0 ? enumB0 : enumB1;
    dispatch(enumA, enumB);
    return 0;
}

caveats

  • 它很好地解释了这个想法而不用优化的互机来解释。
  • std :: Map绝对不是查找表的最适合的数据结构,但是在添加枚举值时, 您将必须在查找表中提供额外的功能专业和几个条目。 在沿着这条路线之前,这将很快变得笨拙
  • ,建议您肯定要对您正在编写的组件的整体设计进行批判性的审查,并评估是否没有更简单的解决方案可以首先避免您的陈述问题改变设计。

奖励

在技术上可以自动创建查找表,从而大大减少向枚举添加更多值所需的工作量。但是,它不能解决组件中的任何设计缺陷,同时肯定会增加复杂性。

template <auto...>
struct EnumAsTypes {};
template <auto... Params>
consteval size_t Size(EnumAsTypes<Params...>) {
    return sizeof...(Params);
}
using ATypes = EnumAsTypes<enumA0, enumA1/*, enumA2*/>;
using BTypes = EnumAsTypes<enumB0, enumB1>;

void dispatch2(EnumA a, EnumB b) {
    static constinit auto lookup = []<EnumA... As, EnumB... Bs>(
                                       EnumAsTypes<As...> as,
                                       EnumAsTypes<Bs...> bs) {
        typedef void (*FunctionType)();
        std::array<FunctionType, Size(as) * Size(bs)> lookup;
        (([&]<EnumA InnerA>() {
             ((lookup[InnerA + sizeof...(As) * Bs] = f<InnerA, Bs>), ...);
         }.template operator()<As>()),
         ...);
        return lookup;
    }(ATypes{}, BTypes{});

    lookup[a + Size(ATypes{}) * b]();
}

您可以在编译器资源管理器这里

If your main concern is avoiding repetitive, nested if/else or switch/case constructs you could create a lookup table instead as Igor pointed out.

For this you could separate the functionality (your "something here" parts) into individual functions that will then be retrieved by your lookup table.

Thus we would remove the branching from your template function. This is now only a catch-all for undefined combinations.

template <EnumA enumA, EnumB enumB>
void f() {
    assert(false);
}

To specify what should happen depending on the combination we can specialize the function template.

template <>
void f<enumA0, enumB0>() {
    // something here
    puts("0-0");
}
template <>
void f<enumA1, enumB0>() {
    // something here
    puts("1-0");
}
template <>
void f<enumA0, enumB1>() {
    // something here
    puts("0-1");
}
template <>
void f<enumA1, enumB1>() {
    // something here
    puts("1-1");
}

Having defined what should happen when, the only gap we have to bridge is how to "convert" enum values known at runtime to the non-type template parameters (aka. enum values known at compile time) we have used in our function definitions. In other words how do we get from enum values passed to a function (e.g. dispatch(enumA1, enumB0) to the call of our function specialization (e.g. f<enumA1, enumB0>). This is where the lookup table comes into play.

void dispatch(EnumA a, EnumB b){
static const std::map<std::pair<EnumA, EnumB>, void(*)()> lookup = {
    {{enumA0, enumB0}, &f<enumA0, enumB0>},
    {{enumA0, enumB1}, &f<enumA0, enumB1>},
    {{enumA1, enumB0}, &f<enumA1, enumB0>},
    {{enumA1, enumB1}, &f<enumA1, enumB1>},
};
    return lookup.at({a, b})();
}

We store the corresponding function pointer to one of the specializations at the key for that combination. Dispatching to the right function is now a simple lookup.
Now your main would look like this:

int main()
{
    int a, b;
    cin >> a >> b;
    const auto enumA = a < 0 ? enumA0 : enumA1;
    const auto enumB = b < 0 ? enumB0 : enumB1;
    dispatch(enumA, enumB);
    return 0;
}

Caveats

  • std::map is definitely not the best-suited data structure for the lookup table, however it nicely explains the idea without obfuscating it with optimizations
  • When adding an enum value you will have to provide the extra function specializations and several entries in the lookup table. This will quickly become cumbersome
  • Before going down this route it is surely recommended to take a critical second look at the overall design of the component you are writing and evaluate whether there isn't a simpler solution that avoids your stated problem in the first place by changing the design.

Bonus

It is technically possible to create the lookup table automatically which drastically reduces the amount of work needed when adding further values to the enums. It does not however solve any design flaws in the component while definitely adding complexity.

template <auto...>
struct EnumAsTypes {};
template <auto... Params>
consteval size_t Size(EnumAsTypes<Params...>) {
    return sizeof...(Params);
}
using ATypes = EnumAsTypes<enumA0, enumA1/*, enumA2*/>;
using BTypes = EnumAsTypes<enumB0, enumB1>;

void dispatch2(EnumA a, EnumB b) {
    static constinit auto lookup = []<EnumA... As, EnumB... Bs>(
                                       EnumAsTypes<As...> as,
                                       EnumAsTypes<Bs...> bs) {
        typedef void (*FunctionType)();
        std::array<FunctionType, Size(as) * Size(bs)> lookup;
        (([&]<EnumA InnerA>() {
             ((lookup[InnerA + sizeof...(As) * Bs] = f<InnerA, Bs>), ...);
         }.template operator()<As>()),
         ...);
        return lookup;
    }(ATypes{}, BTypes{});

    lookup[a + Size(ATypes{}) * b]();
}

You can play around with it on compiler explorer here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文