强制扣除模板参数以引用
Force deduction of template parameter to reference
以下代码无法编译,因为编译器将模板参数推导出为 int
,而要求为 int &
。在 Coliru here.
上查看
#include <iostream>
#include <utility>
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
f(std::forward<ArgsType>(args)...);
}
int main()
{
int s = 2;
int i = 1;
auto add = [](const int a, int& sum) { sum += a; };
do_something(add, i, s);
std::cout << s << std::endl;
return 0;
}
错误:
main.cpp: In instantiation of 'void do_something(F, ArgsType ...) [with F = main()::<lambda(int, int&)>; ArgsType = {int, int}]':
main.cpp:15:27: required from here
main.cpp:7:6: error: no match for call to '(main()::<lambda(int, int&)>) (int, int)'
f(std::forward<ArgsType>(args)...);
~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:7:6: note: candidate: 'void (*)(int, int&)' <conversion>
main.cpp:7:6: note: conversion of argument 3 would be ill-formed:
main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
main.cpp:14:40: note: candidate: 'main()::<lambda(int, int&)>' <near match>
auto add = [](const int a, int& sum) { sum += a; };
^
main.cpp:14:40: note: conversion of argument 2 would be ill-formed:
main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
f(std::forward<ArgsType>(args)...);
~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
理想情况下,do_something
的第三个参数应该推导为 int&
。一种方法是将模板参数显式传递为
#include <iostream>
#include <utility>
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
f(std::forward<ArgsType>(args)...);
}
int main()
{
int s = 2;
int i = 1;
auto add = [](const int a, int& sum) { sum += a; };
do_something<decltype(add), const int, int&>(add, i, s);
std::cout << s << std::endl;
return 0;
}
在 Coliru 上查看 here。
虽然该解决方案有效,但我发现它很不方便,因为它迫使我提供 所有 模板类型 do_something
,这不是最佳选择,尤其是如果,比如说,我有一个更复杂的例子,有几个参数,或者如果我想直接插入 lambda 函数 add
作为参数
do_something
:
do_something([](const int a, int& sum) { sum += a; }, i, s);
有没有更方便的方法强制仅将第三个参数推导为 int &
?
尝试
template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args)
// ^^^^ ---------------> perfect forwarding
{
f(std::forward<ArgsType>(args)...);
}
了解问题
你的 lambda 表达式的第二个参数 (sum
)
auto add = [](const int a, int& sum) { sum += a; };
是一个左值引用。
参数包,ArgsType
在
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args);
在将变量i
和s
分别作为参数传递时推导出参数包int
、int
。
请记住 std::forward<ArgsType>(args)...
与 static_cast<ArgsType&&>(args)...
相同(即,它只是转换为 rvalue 引用 类型),调用do_something()
相当于调用以下函数模板:
template <class F>
void do_something(F f, int a, int b)
{
f(static_cast<int&&>(a), static_cast<int&&>(b));
}
表达式 static_cast<int&&>(b)
是一个 rvalue(更准确地说,是一个 xvalue)。由于您不能使用右值(参数)初始化左值引用(参数),因此会导致编译错误。
解决方案
您可以使用 forwarding references 代替:
template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args);
参数包的单独参数的类型 args
将始终是一个引用:
- 左值 引用类型,如果左值作为参数传递。
- rvalue 引用类型,如果将右值作为参数传递。
这样,对于您的函数调用,参数包 ArgsType
将推导为参数包 int&
,int&
相当于调用以下函数模板:
template <class F>
void do_something(F f, int& a, int& b)
{
f(static_cast<int& &&>(a), static_cast<int& &&>(b));
}
由于 reference collapsing 应用于 static_cast<int& &&>(b)
,表达式结果为 static_cast<int&>(b)
,这是一个左值。左值引用类型可以用左值初始化。
但是,请注意调用:
do_something(add, i, 7);
^
|
--- rvalue
现在不会编译,因为右值作为第二个参数传递给 add
。推理与您原来的错误相似。
以下代码无法编译,因为编译器将模板参数推导出为 int
,而要求为 int &
。在 Coliru here.
#include <iostream>
#include <utility>
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
f(std::forward<ArgsType>(args)...);
}
int main()
{
int s = 2;
int i = 1;
auto add = [](const int a, int& sum) { sum += a; };
do_something(add, i, s);
std::cout << s << std::endl;
return 0;
}
错误:
main.cpp: In instantiation of 'void do_something(F, ArgsType ...) [with F = main()::<lambda(int, int&)>; ArgsType = {int, int}]':
main.cpp:15:27: required from here
main.cpp:7:6: error: no match for call to '(main()::<lambda(int, int&)>) (int, int)'
f(std::forward<ArgsType>(args)...);
~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:7:6: note: candidate: 'void (*)(int, int&)' <conversion>
main.cpp:7:6: note: conversion of argument 3 would be ill-formed:
main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
main.cpp:14:40: note: candidate: 'main()::<lambda(int, int&)>' <near match>
auto add = [](const int a, int& sum) { sum += a; };
^
main.cpp:14:40: note: conversion of argument 2 would be ill-formed:
main.cpp:7:6: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
f(std::forward<ArgsType>(args)...);
~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
理想情况下,do_something
的第三个参数应该推导为 int&
。一种方法是将模板参数显式传递为
#include <iostream>
#include <utility>
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args)
{
f(std::forward<ArgsType>(args)...);
}
int main()
{
int s = 2;
int i = 1;
auto add = [](const int a, int& sum) { sum += a; };
do_something<decltype(add), const int, int&>(add, i, s);
std::cout << s << std::endl;
return 0;
}
在 Coliru 上查看 here。
虽然该解决方案有效,但我发现它很不方便,因为它迫使我提供 所有 模板类型 do_something
,这不是最佳选择,尤其是如果,比如说,我有一个更复杂的例子,有几个参数,或者如果我想直接插入 lambda 函数 add
作为参数
do_something
:
do_something([](const int a, int& sum) { sum += a; }, i, s);
有没有更方便的方法强制仅将第三个参数推导为 int &
?
尝试
template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args)
// ^^^^ ---------------> perfect forwarding
{
f(std::forward<ArgsType>(args)...);
}
了解问题
你的 lambda 表达式的第二个参数 (sum
)
auto add = [](const int a, int& sum) { sum += a; };
是一个左值引用。
参数包,ArgsType
在
template <class F, class... ArgsType>
void do_something(F f, ArgsType... args);
在将变量i
和s
分别作为参数传递时推导出参数包int
、int
。
请记住 std::forward<ArgsType>(args)...
与 static_cast<ArgsType&&>(args)...
相同(即,它只是转换为 rvalue 引用 类型),调用do_something()
相当于调用以下函数模板:
template <class F>
void do_something(F f, int a, int b)
{
f(static_cast<int&&>(a), static_cast<int&&>(b));
}
表达式 static_cast<int&&>(b)
是一个 rvalue(更准确地说,是一个 xvalue)。由于您不能使用右值(参数)初始化左值引用(参数),因此会导致编译错误。
解决方案
您可以使用 forwarding references 代替:
template <class F, class... ArgsType>
void do_something(F f, ArgsType&&... args);
参数包的单独参数的类型 args
将始终是一个引用:
- 左值 引用类型,如果左值作为参数传递。
- rvalue 引用类型,如果将右值作为参数传递。
这样,对于您的函数调用,参数包 ArgsType
将推导为参数包 int&
,int&
相当于调用以下函数模板:
template <class F>
void do_something(F f, int& a, int& b)
{
f(static_cast<int& &&>(a), static_cast<int& &&>(b));
}
由于 reference collapsing 应用于 static_cast<int& &&>(b)
,表达式结果为 static_cast<int&>(b)
,这是一个左值。左值引用类型可以用左值初始化。
但是,请注意调用:
do_something(add, i, 7);
^
|
--- rvalue
现在不会编译,因为右值作为第二个参数传递给 add
。推理与您原来的错误相似。