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

Overloading, with r-value refs, constructor of class template

本文关键字:构造函数 refs 使用 重载      更新时间:2023-10-16

虽然最初它似乎在工作,但当通过maker函数调用(以进行模板参数推导(时,两个重载的存在,一个带有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;
}

有什么想法吗?

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

此函数模板有两个参数,它们是通用引用模板类型参数&&形式的函数模板的参数遵循特殊的推导规则:如果参数是左值,则模板类型参数被推导为左值引用类型。如果参数是右值,则推导的类型不是引用。

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


注意,当参数不是常量时,具有参数int*&的函数比具有参数int* const&的函数更可取。因此,功能模板

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

将不会在出现错误的行中使用。一般来说,通用参考参数是非常贪婪的。

典型的解决方案是使用完美转发,如Jonathan Wakely的回答所述。

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构造函数。

您应该阅读/观看C++11 中的通用参考

我认为这是因为自动类型猜测失败,因为这行代码显示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*&而不是int*。我敢肯定,如果你明确指定类型(我理解你想要避免的(,它会很好地工作。

我真的不明白你为什么把这个放在你的制造商功能中

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

tf已经是右值引用,所以您不需要对它们调用move