【问题标题】:template function pointers and lambdas模板函数指针和 lambda
【发布时间】:2020-02-19 19:41:47
【问题描述】:

您好,我正在尝试使用以下代码解决问题:

template<typename... Args>
using Func = void (*)(Args... args);

template<typename... Args>
void do_test(Func<Args&...> f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

int main(){
    int x = 0;
    do_test(Func<int&>([](int &y) { y++; }), x);  // OK

    // Error - mismatched types 'void (*)(Args& ...)' and 'main()::<lambda(int&)>'
    do_test([](int &y) { y++; }, x);

    return x;
}

https://godbolt.org/z/UaXxFJ

谁能解释为什么有必要将 lambda 包装在 Func&lt;int&amp;&gt;( ) 中?有没有办法避免它? - 因为如果参数列表不平凡,必须列出两次参数类型变得非常乏味。

这里的目标是创建一个编译器可以优化掉的访问者模式。我将它用于图像处理算法,我想重用外部循环的代码,以及各种内部代码。 Args 被用作类似于 lambda 捕获的东西,除了使用传统的函数指针以便编译器可以优化它们之外 - 这似乎无法使用 std::function&lt;&gt;

【问题讨论】:

  • 黑魔法:添加+。示例:do_test(+[](int &amp;y) { y++; }, x);.
  • @FrançoisAndrieux - 什么?太疯狂了。所以在函数指针上做一些整数/指针数学运算,在编译器眼中去掉它的 lamda-ness?
  • 无捕获 lambda 可转换为函数指针。使用+ 会强制进行转换。我不确定的是为什么do_test 不会自动使用这种转换。通常,这表明涉及的两个隐式转换太多了,但我不知道它到底是什么。所以我不确定为什么 not 使用 + 不起作用。

标签: c++ templates lambda variadic


【解决方案1】:

您可以只允许函数采用任何类型,无论是函数指针还是 lambda:

template<typename F, typename... Args>
void do_test(F f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

根据您的用例,考虑使用f by-const-reference 或转发参考(即F&amp;&amp;)。

您还应该考虑更改获取args 函数参数的方式。通常在这种情况下,您会通过转发参考来获取它们,这意味着Args&amp;&amp;... args 而不是Args&amp;... args。否则你将无法使用右值参数调用函数。


或者如果你有一些特定的理由只接受这个特定的函数指针类型,你可以将第一个函数参数设为非推导上下文,因为模板参数已经可以从其他函数参数中推导出来:

template<typename... Args>
void do_test(std::type_identity_t<Func<Args&...>> f, Args&... args) {
    for (int i = 0; i != 100; i++)
      f(args...);
}

std::type_identity_t 是 C++20 的特性,但可以轻松实现:

template<typename T>
struct type_identity {
    using type = T;
};

template<typename T>
using type_identity_t = typename type_identity<T>::type;

type_identity&lt;T&gt;::type 中留给范围解析运算符:: 的所有内容都是非推导上下文,因此第一个函数参数不会用于推导Args,这反过来意味着将考虑隐式转换(例如 lambda 到函数指针的转换)。

或者,正如@FrançoisAndrieux 在问题 cmets 中提到的那样,您可以使用 lambda + 技巧将 lambda 转换为调用站点的函数指针:

do_test(+[](int &y) { y++; }, x);

还请注意,获取此特定类型的函数指针意味着该函数只能使用具有完全此类型的函数调用。例如args 总是被推导出为引用类型,因此任何可能与此一起使用的函数都必须仅采用引用参数。这通常不是你想要的。通常你想要std::function&lt;R(Args...)&gt; 的松散行为,它可以从任何具有指定Args可调用 函数对象构造并返回可以隐式转换为R 的东西。

【讨论】:

  • 您是否知道为什么原始代码不起作用?
  • @FrançoisAndrieux 它尝试在void (*)(Args... args) 中推导出Args... 模板参数,但由于lambda 不是函数指针而失败。
  • @FrançoisAndrieux 即使你提供了一些模板参数,因为它是一个参数包,可能还有更多没有明确指定,所以第一个函数参数仍然需要推导出来。跨度>
  • @walnut 这很有趣。我没有想到这一点。并不是因为你提供了参数包的一部分,它就不能继续推导出更多的参数。
  • @walnut 我明白了,您正在考虑例如do_test([](int &amp;y) { y++; }, 10);。我担心的是给定函数是否有右值引用参数。编辑:我想这是一个不好的例子,因为y 是一个左值引用。也许do_test([](int y) { std::cout &lt;&lt; y &lt;&lt; '\n'; }, 10); 是一个更好的例子
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多