【问题标题】:explicit call to variadic function template with empty parameter pack使用空参数包显式调用可变参数函数模板
【发布时间】:2015-07-15 20:38:59
【问题描述】:

考虑一下这个网站上存在很多变体的简单(坏)函数模板:

template <typename R, typename... Args>
R call_with(std::function<R(Args...)> f,
            Args... args)
{
    return f(args...);
}

两次尝试调用它:

call_with([]{});        // (a)
call_with<void>([]{});  // (b)

我无法调用 (a),因为 lambda 不是 std::function&lt;R(Args...)&gt;,因此模板推导失败。直截了当。

但是,(b) 也失败了。我怀疑这是因为编译器无法确定我的意思是提供 all 我只是提供 R 的类型参数和原因 - 所以它正在尝试(并且失败)推断 Args... 为与初始调用失败的原因相同。

有没有办法明确指定我提供所有模板参数?为了澄清,我对如何显式提供模板参数感兴趣,以便没有模板推导 - 我不是在寻找正确的写法 call_with 或制作模板的方法使用 lambda 调用时,演绎成功。

【问题讨论】:

  • 即使我提供参数,我也会看到失败:coliru.stacked-crooked.com/a/7d2e89a69f8d738d
  • @JayMiller 对,同样的想法。尽管如果我们重写 call_with 以始终采用单个参数 (template &lt;typename R, typename A&gt; R call_with(std::function&lt;R(A)&gt;, A );),那么显式提供模板参数就可以正常工作 (call_with&lt;void, int&gt;([](int ){}, 42);)。固执的只是参数包的情况。
  • 我不应该删除我之前的评论,这让@Barry 澄清这不是关于如何解决这个问题的问题,而被问到的问题正是如所写。 “有没有办法明确指定...”

标签: c++ templates c++11 template-argument-deduction


【解决方案1】:

对您的--edited-- 问题的简短回答是:如果您不能更改call_with() 的声明,那么要么使用@CoffeeandCode 演示的类型转换,要么使用下面描述的技术为@ 创建一个包装器987654323@.

问题在于编译器试图从第一个函数参数中推断出模板参数。如果您编写像this 这样的代码,则可以防止这种情况发生:

#include <functional>
#include <iostream>

// identity is a useful meta-function to have around.
// There is no std::identity, alas.
template< typename T>
struct identity
{
    using type = T;
};

template <typename R, typename... Args>
R call_with( typename identity<std::function<R(Args...)>>::type f,
            Args... args)
{
    return f(args...);
}

int main()
{
    call_with<void>([](int){ std::cout << "called" << std::endl; }, 2);
}

使用模板元函数“生成” std::function 类型意味着编译器甚至无法尝试从第一个参数推断函数类型,它只会使用其他参数。

当然,您仍然需要显式提供返回类型,但是对于其他参数,您现在可以选择是显式指定它们还是让编译器从给定的参数中推导出它们。

如果您真的想强制提供所有模板参数而不是推导,您还可以将参数包包装在 identity 的调用中:

template <typename R, typename... Args>
R call_with( typename identity<std::function<R(Args...)>>::type f,
            typename identity<Args>::type... args)

总而言之,如果你想防止编译器推断出也作为函数参数出现的函数模板参数类型,请将它们包装在元函数中,例如identity

【讨论】:

  • 聪明,绝对让我避免扣分(+1)。虽然需要修改签名,但很不幸。
  • 是的,恐怕有了给定的签名,编译器总是会尝试推断模板参数——除非第一个参数的类型是 std::function .
【解决方案2】:

您可以预先指定函数类型,如下所示:

int main(){
    std::function<void()> f = []{};
    call_with(f);
}

或者,以更凌乱但更紧凑的方式:

int main(){
    call_with(static_cast<std::function<void()>>([]{}));
}

这是因为在您要求编译器将 lambda(编译器定义的未指定函数对象)隐式转换为 std::function 之前,编译器不知道要为模板参数推导出什么返回类型和参数那些模板参数。

真的,您应该将您的函数变形器更改为更通用:

template<typename Functor, typename ... Args>
auto wrapper(Functor &&f, Args &&... args) -> decltype(f(std::forward<Args>(args)...)){
    return f(std::forward<Args>(args)...);
}

这应该适用于任何函数或仿函数类型。这也是使用尾随返回类型的一个非常好的例子。

Here's a live example

【讨论】:

  • 虽然都是正确的,但这根本不能回答问题。这个问题严格来说是关于向call_with 明确提供所有类型,而不是关于如何正确编写该函数。
  • @Barry 我在回答的第一部分给出了一种方法来指定您对call_with 的调用。使用static_cast 只是告诉编译器为这些模板参数放置什么。
  • 不,不是。它正在更改参数类型,以便模板推导能够成功。我正在寻找一个不涉及模板推导的解决方案完全
  • 如何从std::function 更改std::function?您在签名中明确写下std::function,这只是明确地转换 lambda。
  • 当然可以。 template &lt;typename T&gt; void foo(T ) { }; foo&lt;char&gt;(42); 调用 foo&lt;char&gt; 并且不进行模板推导(这将实例化 foo&lt;int&gt;
猜你喜欢
  • 2021-10-01
  • 2011-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多