【问题标题】:Neat way to parametrize function template with generic function pointer使用通用函数指针参数化函数模板的巧妙方法
【发布时间】:2018-01-20 01:30:58
【问题描述】:

考虑以下情况:我有

int bar1();
double bar2();

我想要:

foo<bar1>(); // calls bar1, then uses its result.
foo<bar2>(); // calls bar2, then uses its result.

编写模板foo()的简单方法是使用附加参数:

template <typename T, T (*f)()> void foo () {
  // call f, do something with result
}

这行得通,但我需要做丑陋的语法:

foo<decltype(bar1()), bar1>(); // calls bar1, then uses its result

我想写一些漂亮的东西,就像上面一样,只是foo&lt;bar1&gt;

附:请不要建议在运行时接受参数。我只需要使用函数指针进行编译时参数化。

附:抱歉忘了提:我正在寻找 C++14 解决方案。 C++17 受到赞赏,我对 C++17 解决方案的答案表示赞同,但项目现在使用 C++14 构建,我无法在最近的将来更改它。

【问题讨论】:

  • 你能解释一下为什么它需要是一个模板参数吗?如果你在编译时需要它,将foo 设为constexpr 怎么样?
  • @VaughnCato 如果您将bar1 作为函数指针传递给foo,并且foo 调用bar1,则该调用几乎肯定不会被内联,除非foo 的整体是内联。您可能会发现这令人惊讶,正如我与之交谈过的许多 C++ 专家所期望的那样。但我已经用std::sort 做了十几次这个实验;比较将函数指针与 lambda 传递到 sort(太大而无法内联)。
  • @VaughnCato 您的示例不正确,因为它假设 foo 也被内联。 godbolt.org/g/NTi3oF.
  • @VaughnCato 不正确的意思是您说“在实践中它不应该是间接调用”,并使用该示例来支持它。 foo 不被内联是练习的一部分,所以你的例子不能支持你所说的。绝对没有理由认为这是一个 XY 问题,请不要过度使用它,因为这对提问者来说非常令人沮丧。
  • @VaughnCato,我认为,OP 表现出足够的熟练程度来假设它不是 XY 问题。把一切都归咎于 XY 问题会适得其反。

标签: c++ templates c++14 decltype


【解决方案1】:

为了得到

foo<bar1>();

你需要来自 C++17 的 template&lt;auto&gt;。看起来像

int bar1() { return 1; }
double bar2() { return 2.0; }

template<auto function> void foo() { std::cout << function() << "\n"; }

int main()
{
    foo<bar1>();
    foo<bar2>();
}

哪些输出

1
2

Live Example

在 C++17 之前你必须指定类型,因为非类型模板参数的类型没有自动推导。

【讨论】:

  • 非常感谢,点赞。但是这个解决方案对我来说并不实用(更新的问题)。
  • @KonstantinVladimirov 我看到了。可惜不能升级。我正在尝试寻找不同的解决方案
  • @KonstantinVladimirov 抱歉,如果你想在编译时完成,我看不到没有foo&lt;decltype(bar1()), bar1&gt;(); 的 C++17 之前的方法。也许其他人会有一些东西,但 AFAIK 你必须指定 C++17 之前的类型。
  • 是的,我认为你是对的。 C++17,运行时参数或预处理器——没有其他方法。
【解决方案2】:

因此,我将尝试给出我在 14 中所知道的最佳答案。基本上,解决此问题的一个好方法(恕我直言)是将函数指针“提升”为 lambda。这允许您以更惯用的方式编写 foo 来接受可调用对象:

template <class F>
void foo(F f);

您仍然可以获得最佳性能,因为 lambda 的类型是唯一的,因此它是内联的。不过,您可以更轻松地将 foo 与其他东西一起使用。所以现在我们必须把我们的函数指针变成一个硬编码的 lambda 来调用它。我们在这方面所能做的最好的事情就是从这个问题中得出的:Function to Lambda

template <class T>
struct makeLambdaHelper;

template <class R, class ... Args>
struct makeLambdaHelper<R(*)(Args...)>
{
  template <void(*F)(Args...)>
  static auto make() {
    return [] (Args ... args) {
      return F(std::forward<Args>(args)...);
    };
  }
};

我们这样使用它:

auto lam = makeLambdaHelper<decltype(&f)>::make<f>();

为了避免重复提及,我们可以使用宏:

#define FUNC_TO_LAMBDA(f) makeLambdaHelper<decltype(&f)>::make<f>()

你可以这样做:

foo(FUNC_TO_LAMBDA(bar1)); 

现场示例:http://coliru.stacked-crooked.com/a/823c6b6432522b8b

【讨论】:

  • @skypjack 不,我不明白你的论点。它是函数指针类型的特化。 R(*)(Args...) 是采用Args 并返回R 的函数指针的类型。我认为您对例如使用的语法感到困惑。 std::function。我添加了一个实时示例,如您所见,它按原样工作......
  • 其实我没有得到你的答案的全部内容。它对我来说仍然有点味道,但这是一个答案,OP 将判断它是否适用于真实案例。谢谢。
  • Args &amp;&amp; ... args 完全崩溃了。考虑int i = 1; void f(int); FUNC_TO_LAMBDA(f)(i);
  • 有趣的例子,谢谢。但是 lambda 助手现在似乎是运行时函数参数。我不希望内联会做某事,因为内联有其局限性。
  • @T.C.应该只是Args ... args 吗?我想引用性将从函数签名中推断出来,这样是正确的。
【解决方案3】:

我正在寻找 C++14 解决方案。 C++17 受到赞赏,我对 C++17 解决方案的答案表示赞同,但项目现在使用 C++14 构建,我无法在最近的将来更改它。

不幸的是,您的要求从 C++17 开始有效。

如果你想使用确切的语法

foo<bar1>();

我认为这在 C++14 中是不可能的。

但是,如果您接受稍微不同的语法...我知道宏是邪恶的,但是...如果您接受调用 foo() as

FOO(bar1)();

你可以定义宏

#define FOO(f) foo<decltype(f()), f>

一个完整的工作示例

#include <iostream>

#define FOO(f) foo<decltype(f()), f>

int bar1 ()
 { std::cout << "bar1()" << std::endl; return 0; }

double bar2 ()
 { std::cout << "bar2()" << std::endl; return 1.0; }

template <typename T, T (*f)()>
void foo ()
 { f(); }

int main()
 {
   FOO(bar1)(); // print bar1()
   FOO(bar2)(); // print bar2()
 }

【讨论】:

    【解决方案4】:

    好的,所以您特别要求 bar1bar2 成为函数,但是如果您愿意放宽该约束,而是允许它们成为具有实现所需行为的静态成员函数的类,您可以做以下方式,甚至不需要 C++11 -

    struct bar1 {
      static int f() { return 42; }
    };
    
    struct bar2 {
      static double f() { return 3.14159; }
    };
    
    template<typename bar>
    void foo()
    {
      double x = bar::f();
      std::cout << x << std::endl;
    }
    
    int main(int argc, char* const argv[])
    {
      foo<bar1>();
      foo<bar2>();
    }
    

    【讨论】:

      【解决方案5】:

      这个怎么样?

      template<typename F>
      auto foo(F* f)
      {
          return f();
      }
      
      int bar() { return 1; }
      
      int main()
      {
          return foo(bar);
      }
      

      【讨论】:

      • 来自问题:“P.S. 请不要在运行时接受参数。我只需要使用函数指针进行编译时参数化。”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-09
      • 2017-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多