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

时间:2014-07-15 10:10:03

标签: c++ c++11

虽然最初似乎是working,但是当通过制造商函数调用(具有模板参数推导)时,存在两个重载,一个带有T const&,一个带有T&& breaks compilation

#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;
}

有什么想法吗?

3 个答案:

答案 0 :(得分:6)

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

此函数处理左值和右值。当您调用test(N, F)时,参数是非常量左值,因此使用上面的重载, 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) };
}

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

您应该阅读/观看Universal References in C++11

答案 1 :(得分:6)

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

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

auto m = test(N, F);

int* Nfunction<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) 

将生成相同的签名,因为int*& &&int*& const&都折叠为int*&,类似于F


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

template<typename T, typename F>
Test<T, F> test(T const& t, F const& f)

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

典型的解决方案是使用<{3}} Jonathan Wakely中所述的完美转发

答案 2 :(得分:0)

我认为这是因为自动类型猜测失败了,因为这句话说prog.cpp:18:2: error: ‘Test<T, F>::Test(const T&, const F&) [with T = int*&; F = std::function<void(int*)>&]’ cannot be overloaded你的编译器试图用int *&amp;来实例化它。而不是int *。我非常确定你是否明确指定了类型(我明白你想要避免什么)它会正常工作。

而且我真的不明白为什么你把它放在你的制造商功能中

Test<T, F> test(T&& t, F&& f)
{
    return Test<T, F>(move(t), move(f));
}

tf已经是左值参考,因此您不需要调用它们移动