可变参数模板类中的C ++ 11 Variadic函数无法按预期工作

时间:2016-02-17 09:09:39

标签: c++11 variadic-templates variadic-functions

如果在其他地方得到回答,我真的道歉,我确实进行了搜索,但找不到明确的匹配。

我在模板类中有一个可变函数,它不能完全按照我的预期工作。我有一个解决方法,但我怀疑它不是最好的解决方案。

请考虑以下代码:

#include <iostream>
#include <functional>
#include <vector>

template< typename... ARGS >
class Kitten
{
public:
    using Callback = std::function< void( ARGS&&... ) >;

    Kitten() = default;

    void addCallback( Callback && c )
    {
        callbacks.push_back( std::forward< Callback >( c ) );
    }

    void processCallbacks( ARGS &&... args )
    {
        for ( Callback const &c : callbacks )
        {
            c( std::forward< ARGS >( args )... );
        }
    }

private:
    std::vector< Callback > callbacks;
};

int main( int argc, const char * argv[] )
{
    ( void ) argc;
    ( void ) argv;

    Kitten<int, float> kitty;
    kitty.addCallback( []( int i, float f )
    {
        std::cout << "Int: " << i << "\nFloat: " << f << "\n";
    } );
    kitty.processCallbacks( 2, 3.141f );

    int ii = 54;
    float ff = 2.13f;
    kitty.processCallbacks( ii, ff );

    return 0;
}

这将无法编译,对processCallbacks的第二次调用将生成错误(clang,类似于vc14上的问题)。

如果我将processCallbacks的定义更改为:

,我可以修复编译并按预期工作
template< typename... FORCEIT >
void processCallbacks( FORCEIT &&... args )
{
    for ( Callback const &c : callbacks )
    {
        c( std::forward< ARGS >( args )... );
    }
}

在我看来,即使它似乎有效,也是一种俗气的解决方法,我怀疑我错过了一个更好的解决方案。

我对第一个示例失败原因的理解是因为在参数包上没有进行类型推导,因此编译器没有为所有情况生成正确的代码。第二个示例有效,因为它强制对参数包进行类型推导。

我一直困扰着我一段时间。任何帮助非常感谢。

编辑:vc12编译错误:

error C2664: 'void Kitten<int,float>::processCallbacks(int &&,float &&)' : cannot convert argument 1 from 'int' to 'int &&'

编辑:Apple LLVM版本7.0.0编译器错误:

error: rvalue reference to type 'int' cannot bind to lvalue of type 'int'

关于使用std::move的评论中建议的更改,addCallback似乎在表单中更加灵活:

template< typename FUNCTION >
void addCallback( FUNCTION && c )
{
    callbacks.emplace_back( std::forward< FUNCTION >( c ) );
}

使用std::forward因为该函数现在采用通用引用。 因为这将允许以下工作:

std::function< void( int, float )> banana( []( int i, float f )
{
    std::cout << "Int: " << i << "\nFloat: " << f << "\n";
} );
kitty.addCallback( banana );

1 个答案:

答案 0 :(得分:3)

void processCallbacks( ARGS &&... args )
{
    //...
}

对于ARGS中的每种类型,允许的值类别将由Kitten的模板参数设置。例如。对于Kitten<float, int&, const bool&>processCallbacks将接受第一个参数的rvalues,第二个参数的lvalues(由于reference collapsing rules)和第三个参数(因为rvalues可以绑定到const lvalue引用)。这不会给你完美的转发。

template< typename... FORCEIT >
void processCallbacks( FORCEIT &&... args )
{
    //...
}

该函数模板同时接受左值和右值,因为存在类型推导。这样的参数称为转发参考参数。转发参考参数的格式必须为T&&,其中T是推导出的模板参数。

如果你想接受左值和右值并完美转发它们,你应该使用后一种形式。虽然起初看起来很奇怪,但这是一种相当常见的模式。