使用r值refs重载,类模板的构造函数

Nik*_*iou 1 c++ c++11

虽然最初似乎工作,当通过制造商函数调用(具有模板参数推导)时,存在两个重载,T const&一个带有T&& 中断和一个带有中断编译:

#include <iostream>
#include <utility>
#include <functional>

using namespace std;

// -----------------------------------------------
template<typename T, typename F>
struct Test
{
    T m_resource;
    F m_deleter; 

    Test(T&& resource, F&& deleter) 
    : m_resource(move(resource)), m_deleter(move(deleter))
    {
    }
    Test(T const& resource, F const& deleter) 
    : m_resource(resource), m_deleter(deleter)
    {
    }
};
// -----------------------------------------------

// -----------------------------------------------
template<typename T, typename F>
Test<T, F> test(T&& t, F&& f)
{
    return Test<T, F>(move(t), move(f));
}
template<typename T, typename F>
Test<T, F> test(T const& t, F const& f)
{
    return Test<T, F>(t, f);
}
// -----------------------------------------------

int main() 
{
    // construct from temporaries --------------------
    Test<int*, function<void(int*)>> t(new int, [](int *k) {}); // OK - constructor
    auto tm = test(new int, [](int *k){});                      // OK - maker function
    // -----------------------------------------------

    // construct from l-values -----------------------
    int *N = new int(24);
    auto F = function<void(int*)>([](int *k){});

    Test<int*, function<void(int*)>> tt(N, F); // OK    - construction
    auto m = test(N, F);                       // Error - maker function
    // -----------------------------------------------

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗 ?

Jon*_*ely 6

template<typename T, typename F>
Test<T, F> test(T&& t, F&& f)
{
    return Test<T, F>(move(t), move(f));
}
Run Code Online (Sandbox Code Playgroud)

此函数处理左值和右值.当你调用test(N, F)参数是非const左值时,所以使用上面的重载,而不是那个test(const T&, const F&).这会实例化您的类模板,Test<int*&, std::function<void(int*)>&>而不是您想要的.

您只需要一个制造商功能:

template<typename T, typename F>
auto test(T&& t, F&& f)
-> Test<typename std::decay<T>::type, typename std::decay<F>::type>
{
    return { std::forward<T>(t), std::forward<F>(f) };
}
Run Code Online (Sandbox Code Playgroud)

这将接受左值和右值的任意组合,它将确保您返回所需的类型,并且它将Test使用其原始值类别将参数完美地转发给构造函数.

您应该在C++ 11中阅读/观看通用引用

  • @NikosAthanasiou一个`vector`可能没有被实例化为引用作为值类型,而`push_back`不是一个函数模板(重载是`push_back(value_type &&)`和`push_back(value_type const&)`.对于`vector <vector < double >>`,`push_back(vector <double> &&)`允许调用`push_back({1.0,2.0,3.0})` (2认同)

dyp*_*dyp 6

template<typename T, typename F>
Test<T, F> test(T&& t, F&& f)
{
    return Test<T, F>(move(t), move(f));
}
Run Code Online (Sandbox Code Playgroud)

此函数模板有两个参数,即通用引用.form -type-parameter 形式的函数模板参数&&遵循特殊推导规则:如果参数是左值,则模板类型参数推导为左值引用类型.如果参数是rvalue,则推导的类型不是引用.

auto m = test(N, F);
Run Code Online (Sandbox Code Playgroud)

int* N并且function<void(int*)> F是左值,因此对于上面的函数模板,T推导出int*&F推导出function<void(int*)>&.参考折叠适用,参数T&&变为int*& &&并折叠为int*&(类似地F&&).

因此,您的类模板使用引用类型(T == int*&,F == function<void(int*)>&)进行实例化.在课堂模板中,

Test(T&& resource, F&& deleter)
Test(T const& resource, F const& deleter) 
Run Code Online (Sandbox Code Playgroud)

将产生相同的签名,因为int*& &&并且int*& const&都折叠到int*&,同样地F.


请注意,当参数不是const时,int*&带参数的函数优先于带参数int* const&的函数.因此,功能模板

template<typename T, typename F>
Test<T, F> test(T const& t, F const& f)
Run Code Online (Sandbox Code Playgroud)

不会在出现错误的行中使用.通常,通用参考参数非常贪婪.

典型的解决方案是使用Jonathan Wakely回答中描述的完美转发.