可变参数模板 class 中的 C++11 可变参数函数未按预期工作

C++11 Variadic function in variadic template class not working as expected

如果此问题已在其他地方得到解答,我深表歉意,我进行了搜索但找不到明确的匹配项。

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

考虑以下代码:

#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 );
void processCallbacks( ARGS &&... args )
{
    //...
}

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

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

该函数模板接受左值和右值,因为存在类型推导。这样的参数被称为转发参考参数。转发引用参数必须采用 T&& 形式,其中 T 是一些推导的模板参数。

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