【问题标题】:C++11 Template function that takes a std::function which depends of template parametersC++11 模板函数,它接受一个依赖于模板参数的 std::function
【发布时间】:2011-09-30 09:57:13
【问题描述】:

我正在尝试编写一个接受 std::function 的模板函数,这取决于模板参数。不幸的是,编译器无法正确推断std::function 的参数。这里有一些简单的示例代码:

#include <iostream>
#include <functional>

using namespace std;

void DoSomething( unsigned ident, unsigned param )
{
    cout << "DoSomething called, ident = " << ident << ", param = "  << param << "\n";
}

template < typename Ident, typename Param >
void CallFunc( Ident ident, Param param, std::function< void ( Ident, Param ) > op )
{
    op( ident, param );
}

int main()
{
    unsigned id(1);
    unsigned param(1);

    // The following fails to compile
    // CallFunc( id, param, DoSomething );

    // this is ok 
    std::function< void ( unsigned, unsigned ) > func( DoSomething );
    CallFunc( id, param, func ); 

    return 0;
}

如果我使用以下内容调用模板:

CallFunc( id, param, DoSomething );

我收到以下错误:

function-tpl.cpp:25: 错误:没有匹配的函数调用CallFunc(unsigned int&amp;, unsigned int&amp;, void (&amp;)(unsigned int, unsigned int))

如果我明确地创建了一个正确类型的 std::function(或强制转换它),问题就会消失:

std::function< void ( unsigned, unsigned ) > func( DoSomething );
CallFunc( id, param, func );

我将如何编码这样就不需要显式临时?

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    您需要使第三个函数参数成为其中模板参数的非推导上下文。然后编译器不会在不考虑所有隐式转换的情况下将参数类型与参数类型进行比较(标准说,并且 C++0x 进一步澄清了这一点,对于推导位置中没有模板参数的函数参数,所有隐式在弥合差异时允许转换)。

    template < typename T > struct id { typedef T type; };
    
    template < typename Ident, typename Param >
    void CallFunc( Ident ident, Param param, 
                   typename id<std::function< void ( Ident, Param ) >>::type op )
    {
        op( ident, param );
    }
    

    您可以使用boost::identity 代替id。在支持它的 C++0x 和编译器中,您可以使用别名模板获得更易读的版本

    template < typename T > using nondeduced = typename id<T>::type;
    

    那么你的代码就变得简单了

    template < typename Ident, typename Param >
    void CallFunc( Ident ident, Param param, 
                   std::function< nondeduced<void ( Ident, Param )> > op )
    {
        op( ident, param );
    }
    

    但是 GCC 还不支持别名模板。

    【讨论】:

    • 我会认为参数已经在非推断上下文中。很高兴知道这一点!
    • 为什么第一个版本包裹了整个function类型,而第二个版本只包裹了模板参数?
    • @KerrekSB 由于编译器对 C++03 中措辞的不同解释,我将整个参数类型包装到非推导上下文中。一些编译器会在进入非推导上下文之前选择类型的顶级部分(即std::function&lt;void(..., ...)&gt; 部分)并将其与参数进行比较,从而触发推导失败。其他编译器不会这样做。如果环绕整个类型,则不会留下“未触及”的部分,所以都是...(黑洞),不会出现不匹配的情况。见open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1184
    • C++11 中明确的规则是,如果我理解正确的话,它们会比较未触及的部分,即他们比较 std::function&lt;void(..., ...)&gt;FunctionType *,然后发现不匹配。但是在发现不匹配时,如果类型仅包含非推导上下文中的模板参数,它将允许所有隐式转换来弥补这一点。
    【解决方案2】:

    如果您使用模板,则可以完全避免使用std::function,除非出于某种原因您想特别限制该函数采用std::function

    template < typename Ident, typename Param, typename Func >
    void CallFunc( Ident ident, Param param, Func op )
    {
        op( ident, param );
    }
    

    【讨论】:

    • 您是否忘记删除违规行前的注释字符“//”?我有 GCC4.6.1 并且 GCC 拒绝了上述行。
    【解决方案3】:

    您可以进行内联转换或使用bind。两者都不是特别漂亮,但它们完成了工作

    CallFunc(id, param, std::function<void(unsigned, unsigned)>(DoSomething));
    

    CallFunc(id, param, std::bind(DoSomething, std::placeholders::_1, std::placeholders::_2));
    

    【讨论】:

    • std::bind 产生以下错误:function-tpl.cpp:37: error: no matching function for call to 'CallFunc(unsigned int&, unsigned int&, std::_Bind, std::_Placeholder))(unsigned int, unsigned int)>)'
    • @mark:你说得对,bind 不允许参数推导,你必须说CallFunc&lt;unsigned, unsigned&gt;(...)。所以......只需使用显式转换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2021-10-30
    • 2018-05-12
    • 2021-10-18
    相关资源
    最近更新 更多