可变参数模板 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
是一些推导的模板参数。
如果你想接受左值和右值并完美转发它们,你应该使用后一种形式。虽然起初您可能觉得很奇怪,但这是一种相当常见的模式。
如果此问题已在其他地方得到解答,我深表歉意,我进行了搜索但找不到明确的匹配项。
我在模板 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
是一些推导的模板参数。
如果你想接受左值和右值并完美转发它们,你应该使用后一种形式。虽然起初您可能觉得很奇怪,但这是一种相当常见的模式。