【问题标题】:Conditional templated type aliases in scope of templated function模板函数范围内的条件模板类型别名
【发布时间】:2019-04-09 01:13:17
【问题描述】:

假设我有 N 具有一组相同参数的函数。我调用带有参数的工厂方法,以及一些允许我在 N 函数之间进行选择的值。

一个不整洁的方法是像这样做一个 if 语句:

if(choice == 1)
    foo_one<T>(args);
else if (choice == 2)
    foo_two<T>(args);
etc

这很好,除非在我的情况下参数数量很大。因此工厂方法非常拥挤。

假设我有一个调用工厂方法的主函数:

int main(int argc, char *argv[])
{
    int choice = 1;
    float arg = 1.5;
    foo_choose(choice, arg);
    return 0;
}

我正在考虑在 choice 参数上进行分支,并在该 if 语句中设置函数别名。

这意味着无论N 有多大,我只需要传递一次参数。

它可能看起来像这样:

template <typename T>
void foo_one(T arg) { std::cout << "foo_one\n"; };

template <typename T>
void foo_two(T arg) { std::cout << "foo_one\n"; };

template <typename T>
void foo_choose(int choice, T arg)
{
    if(choice == 1)
        using foo = foo_one<T>;
    else
        using foo = foo_two<T>;        

    foo(arg);
};

但是,我无法让它与模板一起使用。是否有明显的我遗漏的东西,或者我应该添加的搜索词。

由于我可能多次调用 foo_choose 并使用不同的 choice 值,我不希望别名 foo 存在于函数范围之外。

This question suggests也许我走错了路,但我不确定。

【问题讨论】:

  • 要在编译时选择模板中的内容,您需要if constexpr。但是你不能检查像choice 这样的东西,因为它是一个运行时参数。
  • 所以using 是在编译时而不是运行时应用的东西?
  • 是的。 using 有多种不同的用途,但它们都是编译时的东西。

标签: c++ templates alias


【解决方案1】:

问题

template <typename T>
void foo_choose(int choice, T arg)
{
    if(choice == 1)
        using foo = foo_one<T>;
    else
        using foo = foo_two<T>;        

    foo(arg);
};

是说 using 语句是作用域语句。这意味着它们仅适用于声明的范围,因此一旦您到达foo(arg);,foo 就会超出范围并且您会收到错误消息。您可以获取指向它的指针,而不是获取函数的别名,然后在最后调用该指针。看起来像

template <typename T>
void foo_choose(int choice, T arg)
{
    using f_ptr = void(*)(T);
    f_ptr foo;
    if(choice == 1)
        foo = foo_one<T>;
    else
        foo = foo_two<T>;        

    foo(arg);
};

这确实伴随着您必须指定所有函数模板参数的警告。

【讨论】:

  • 应该提到这需要 1. foo_one 没有被重载和 2. foo_one 的所有模板参数被拼写(如果有的话)。
  • @MaxLanghof 添加了有关模板的信息。如果它被重载,它就可以工作,因为赋值充当静态强制转换。见:coliru.stacked-crooked.com/a/a79550b93139db50
  • 这是我需要的一个很好的解决方案,谢谢。 @MaxLanghof,谢天谢地 2. 不适用于我的用例,但我可以想象这是一个问题
【解决方案2】:

如果您的所有候选函数都是相同类型的函数,您可以简单地设置一个函数指针的本地数组并从中进行选择:

template <typename T>
void foo_choose(int choice, T arg)
{
    constexpr void (*funcs[])(T) = { foo_one<T>, foo_two<T> };

    assert(choice > 0 && choice < std::extent_v<decltype(funcs)>);

    return funcs[choice](arg);
};

live example here

这里的其他答案已经给出了为什么您的原始方法不起作用的解释。我不认为我可以添加...

【讨论】:

  • 最好使用std::function 而不是使用函数指针。这将允许 foo_one&lt;T&gt;, foo_two&lt;T&gt; 等成为简单的 可调用对象 并且更通用。
  • @πάντα ῥεῖ 但它也会产生潜在的显着运行时开销,而不是完全编译成虚无的 constexpr 构造。因此,除非您真的需要可调用对象的异构映射,否则我不会碰std::function。就个人而言,在接触std::function 之前,我很可能只是在这里使用 switch/if 级联……或者重新考虑是否真的有必要基于整数索引在不同的函数实现之间切换……
  • 嗯,我首先想到的是使用面向 SFINAE 的解决方案,而不是应用运行时开销。不确定这是否是从胆量开始的正确轨道。虽然我相信使用std::function 带来的开销将完全伴随具体可调用对象的实例化。如果它是一个简单的函数指针,包装它不会花费太多,或者至少完全被编译器优化。如果它是更复杂的东西,比如 class 类型或 lambda 表达式,那么性能开销就在那里。
  • @πάντα ῥεῖ 我认为这里的主要问题是,由于std::function 的性质,实现必须依赖类型擦除内容和/或运行时多态性,这将给优化器一段艰难的时光。此外,我们的函数映射必须是,例如,动态初始化的静态局部变量。似乎任何主要编译器都无法看穿使用 std::function 的东西,即使我们只是使用它来包装普通函数指针:godbolt.org/z/PMpjGP 将此与 godbolt.org/z/6zpw-0 进行比较……
  • 关于优化的好推理,是的。
【解决方案3】:

小点

没有必要使用

using foo = foo_one<T>;

紧随其后

foo(arg);

你也可以使用

foo_one(arg);

更重要

正如您已经注意到的,使用级联 if/else 语句是一个维护问题,很容易导致错误。

如果你可以保留一个函数/仿函数的映射,可以用来分派对适当函数的调用,那就更好了。但是,对于函数模板,我认为没有明确的通用类型可以用来创建函数/仿函数的映射。

让我们回家吧,其他人有正确的想法来证明它是可以做到的。

【讨论】:

  • 小点是公平的,但是当 args 的数量很大时,连同 foos 的数量一起,foo_choose 的单个函数声明可能会非常大。我正在重构,因此我已经看到这种方法会变得多么丑陋。 @NathanOliver 的回答似乎可以满足我的需求,但我想知道您是否认为有任何弱点。
  • @PrunusPersica,只不过是我在回答中已经提到的。
猜你喜欢
  • 1970-01-01
  • 2014-01-29
  • 2018-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-26
相关资源
最近更新 更多