虽然最初似乎工作,当通过制造商函数调用(具有模板参数推导)时,存在两个重载,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)
有任何想法吗 ?
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使用其原始值类别将参数完美地转发给构造函数.
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的回答中描述的完美转发.