2016-02-17 56 views
1

如果已在其他地方回答了此问题,表示歉意,我确实搜索过,但找不到明确的匹配项。C++ 11可变参数模板类中的可变参数函数不能按预期方式工作

我在模板类中有一个可变参数函数,它不能像我期望的那样工作。我有一个解决方法,但我怀疑这不是最好的解决方案。

考虑下面的代码:

#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会产生一个错误(铛,看到VCl 4类似的问题)。

我可以修复编译和得到的东西,如果我改变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 &&' 

编辑:苹果LLVM版本7.0.0编译器错误:

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

关于变化的意见建议使用std::moveaddCallback似乎在形式上更加灵活:

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); 
+2

你应该发布你的编译器错误。 – TartanLlama

+1

'std :: forward < Callback >(c)'等于'std :: move(c)' –

+0

大概我还应该使用'emplace_back'来提高效率? – Slartibartfast

回答

3
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是一些推导出的模板参数。

如果你想接受左值和右值并完善它们,你应该使用后一种形式。尽管起初你可能看起来很奇怪,但这是一个相当常见的模式。

相关问题