当前位置: 首页 > 知识库问答 >
问题:

模板参数和 std::函数参数的演绎

牟波
2023-03-14

假设有一个模板函数 foo() 接受任意数量的参数。给定最后一个参数始终是 std::函数,我如何实现下面显示的 foo() 模板,以便 CbArgs 包含此 std::function 的参数?

template<typename... InArgs, typename... CbArgs = ???>
//                                       ^^^^^^^^^^^^
void foo(InArgs... args) { ... }

例如,如果像这样调用,CbArgs应该是{int,int}

std::function<void(int,int)> cb;
foo(5, "hello", cb);

我的第一个想法是:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs... args, std::function<void(CbArgs...)>) { ... }

但这不会编译:

note:   template argument deduction/substitution failed:
note:   mismatched types ‘std::function<void(CbArgs ...)>’ and ‘int’
  foo(5, "hello", cb);

问题一:
为什么这不能编译?为什么模板参数演绎失败?

最终,我想出了这个解决方案:

template<typename... InArgs, typename... CbArgs>
void fooImpl(std::function<void(CbArgs...)>, InArgs... args) { ... }

template<typename... InArgs,
         typename CbType = typename std::tuple_element_t<sizeof...(InArgs)-1, std::tuple<InArgs...>>>
void foo(InArgs... args)
{
    fooImpl(CbType{}, args...);
}

这里,CbTypeInArgs,即std::function中的最后一种类型。然后,CbType的临时值被传递到fooImpl(),其中CbArgs。这行得通,但在我看来很难看。

问题二:
我想知道如果没有两个函数和CbType的临时实例,是否有更好的解决方案?

共有1个答案

郁高韵
2023-03-14

这个为什么不编译?模板参数推演为什么会失败?

当参数包不是最后一个参数时,无法推导它。告诉编译器InArgs...的内容将使您的foo定义工作:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs..., std::function<void(CbArgs...)>) { }

int main()
{
    std::function<void(int,int)> cb;
    foo<int, const char*>(5, "hello", cb);
}

或者,正如您在解决方法中发现的那样,只需将InArgs...放在末尾并更新您的foo调用:

template<typename... InArgs, typename... CbArgs>
void foo(std::function<void(CbArgs...)>, InArgs...) { }

int main()
{
    std::function<void(int,int)> cb;
    foo(cb, 5, "hello");
}

我想知道如果没有两个函数和<code>CbType</code>的临时实例,是否有更好的解决方案?

这里有一种可能的方法可以避免不必要的临时实例,但使用相同的机制来推导CbArgs…:只需将CbType包装在一个空包装器中,然后将其传递给fooImpl

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

template<typename... InArgs, typename... CbArgs>
void fooImpl(type_wrapper<std::function<void(CbArgs...)>>, InArgs&&...) { }

template<typename... InArgs,
         typename CbType = 
             std::tuple_element_t<sizeof...(InArgs)-1, 
                 std::tuple<std::remove_reference_t<InArgs>...>>>
void foo(InArgs&&... args)
{
    fooImpl(type_wrapper<CbType>{}, std::forward<InArgs>(args)...);
}

其他改进:

>

  • typename.CbType=之后的typename,是不必要的-它被删除了。

    < code >参数...应该完全转发到< code>fooImpl以保留其值类别。< code>foo和< code>fooImpl都应该采用< code >参数...作为转发参考。

    Wandbox示例

    请注意,有一个建议可以使处理非终端参数包更容易:P0478R0-“非终端函数参数包的模板参数推导”。这将使您的原始实现按预期工作。

  •  类似资料:
    • 我目前有一个,但是为了灵活性,我希望能够分配一个lambda表达式,将作为映射中的值返回。 所以我创建了这个模板类: 并像这样使用它: IntelliSense提供了更多信息: 多个操作符“=”匹配这些操作数:function“valueorfunction::operator=(const std::function&other)[with T=std::wstring]”function“va

    • 如果我没有理解错的话,类模板定义了一个函数,所以当我调用时,编译器有可能进行隐式强制转换,但是在函数模板的情况下,此时没有函数定义,所以隐式强制转换不会发生。 但我不明白为什么编译器不能创建函数定义,然后应用隐式强制转换? 错误是: 在函数“int main()”中:    25:24:错误:调用“test2::add(void(&)(int))”没有匹配函数    25:24:注:候选人是:  

    • 我正在构建一些输入检查器,需要为整数和/或双精度设置特定的函数(例如,“iPrime”应该只适用于整数)。 如果我使用作为参数,它工作得很好: 但如果我将其用作模板参数(如上所示)http://en.cppreference.com/w/cpp/types/enable_if ) 那么我有以下错误: 我不知道第二个版本出了什么问题。

    • 这是在参数是重载函数时重载解析如何工作中提到的更复杂的问题? 下面的代码编译起来没有任何问题: 模板参数推导似乎不是一项具有挑战性的任务-只有一个函数接受两个参数。但是,取消注释的模板重载(仍然只有一个参数)会无缘无故地破坏编译。gcc 5. x/6. x和clang 3.9的编译都失败了。 它可以用重载解析/模板参数推导规则来解释,还是应该在这些编译器中被限定为缺陷?

    • 请考虑以下定义和演绎指南: 如果我尝试使用显式模板参数实例化 ,代码将正确编译: 如果我试图通过演绎指南实例化< code>foo... > G7产生编译器错误: clang 5 爆炸: wandbox上的实例 虽然clang肯定被窃听了(报告为问题#32673),但g拒绝我的代码是正确的吗?我的代码格式错误吗?

    • 我希望维护语法,但似乎只能将typenames放在括号中,而不能将类放在括号中。以下是我当前的代码: 问题1:我找不到一种方法来使“default”参数对于特殊化不是必需的。我尝试了使用默认构造函数的代理类,将第三个参数更改为指针,并指定nullptr(这在语法上并不理想)和对类型的常量引用(这仍然需要用户端的三个参数),但似乎没有任何东西允许在中接受两个参数。 问题2:我找不到正确的语法来获得混