如何将右值引用参数传递给 C++ 中的模板 operator() 函数?
How to pass a rvalue reference parameter to a template operator() function in C++?
我尝试编写一些代码来实现 C++17 中的柯里化函数。我的当前实现在下面(我将在这个问题的底部给你一个最小的工作示例)。
template <class Function, class... CapturedArgs>
class curried{
private:
using CapturedArgsTuple = std::tuple<std::decay_t<CapturedArgs>...>;
template <class... Args>
static auto capture_by_value(Args&&... args){
return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
}
public:
curried(Function function, CapturedArgs&&... args)
: m_function(function), m_capture(capture_by_value(std::move(args)...)){}
curried(Function function, std::tuple<CapturedArgs...> args)
: m_function(function), m_capture(std::move(args)){}
template <class... NewArgs>
auto operator()(NewArgs&&... args){
auto new_args = capture_by_value(std::forward<NewArgs>(args)...);
auto all_args = std::tuple_cat(m_capture, new_args);
if constexpr(std::is_invocable_v<Function, CapturedArgs..., NewArgs...>){
return std::apply(m_function, all_args);
}else{
return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
}
}
private:
Function m_function;
std::tuple<CapturedArgs...> m_capture;
};
这是一个测试函数:
void func(const string& str1, string& str2, string str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!";
auto test = curried(func);
auto test_two = test(std::cref(str1))(std::ref(str2));
cout << "result : ";
test_two(str3);
}
到目前为止一切顺利。我可以在我的终端上看到一些日志打印,例如:
$ result : str1 = Hello , str2 = Worldstr2 , str3 = !
我在这里有两个问题:
第一个是如何通过传递右值引用来调用柯里化函数?我已经尝试了所有可以搜索的方法,但结果要么是编译错误或者什么都没有。
void func_1(const string& str1, string& str2, string&& str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!";
auto test = curried(func_1);
auto test_two = test(std::cref(str1))(std::ref(str2));
cout << "result : ";
// test_two(std::move(str3)); Compile Error
// test_two(string("!")); Compile Error
test_two(std::bind(std::move<string&>, str3)); // Compile successfully, but there's nothing output
}
在解决第一个问题的过程中,我发现了一些奇怪的事情。这是一个例子:
void func_2(const string& str1, string& str2, string str3, string& str4){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3
<< ", str4 = " << str4 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!", str4 = "abc";
auto test = curried(func_2);
auto test_two = test(std::cref(str1))(std::ref(str2))(str3);
cout << "result : ";
test_two(std::ref(str4));
}
当我使用 func_2 测试我的柯里化函数时,我收到一些错误消息:
$ g++ curried.cc -std=c++17
curried.cc: In instantiation of ‘auto curried<Function, CapturedArgs>::operator()(NewArgs&& ...) [with NewArgs = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}; Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >}]’:
curried.cc:60:15: required from here
curried.cc:28:11: error: no matching function for call to ‘curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(void (*&)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’
28 | return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
curried.cc:18:2: note: candidate: ‘curried<Function, CapturedArgs>::curried(Function, std::tuple<_Elements ...>) [with Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}]’
18 | curried(Function function, std::tuple<CapturedArgs...> args)
| ^~~~~~~
curried.cc:18:57: note: no known conversion for argument 2 from ‘tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>’ to ‘tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>’
18 | curried(Function function, std::tuple<CapturedArgs...> args)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
curried.cc:15:2: note: candidate: ‘curried<Function, CapturedArgs>::curried(Function, CapturedArgs&& ...) [with Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}]’
15 | curried(Function function, CapturedArgs&&... args)
| ^~~~~~~
curried.cc:15:2: note: candidate expects 4 arguments, 2 provided
curried.cc:7:7: note: candidate: ‘constexpr curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(const curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&)’
7 | class curried{
| ^~~~~~~
curried.cc:7:7: note: candidate expects 1 argument, 2 provided
curried.cc:7:7: note: candidate: ‘constexpr curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&)’
curried.cc:7:7: note: candidate expects 1 argument, 2 provided
curried.cc: In function ‘int main()’:
curried.cc:60:10: error: void value not ignored as it ought to be
60 | test_two(str3)(std::ref(str4));
| ~~~~~~~~^~~~~~
所以第二个问题是为什么我不能把字符串参数str3
放在字符串引用前str4
? 为什么参数的顺序定义问题让我完全不知所措。
为方便起见,这里有一个最小的工作示例:
#include <iostream>
#include <functional>
#include <tuple>
using namespace std;
template <class Function, class... CapturedArgs>
class curried{
private:
using CapturedArgsTuple = std::tuple<std::decay_t<CapturedArgs>...>;
template <class... Args>
static auto capture_by_value(Args&&... args){
return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
}
public:
curried(Function function, CapturedArgs&&... args)
: m_function(function), m_capture(capture_by_value(std::move(args)...)){}
curried(Function function, std::tuple<CapturedArgs...> args)
: m_function(function), m_capture(std::move(args)){}
template <class... NewArgs>
auto operator()(NewArgs&&... args){
auto new_args = std::make_tuple(std::forward<NewArgs>(args)...);
auto all_args = std::tuple_cat(m_capture, std::move(new_args));
if constexpr(std::is_invocable_v<Function, CapturedArgs..., NewArgs...>){
return std::apply(m_function, all_args);
}else{
return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
}
}
private:
Function m_function;
std::tuple<CapturedArgs...> m_capture;
};
void func_1(const string& str1, string& str2, string&& str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
void func_2(const string& str1, string& str2, string str3, string& str4){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3
<< ", str4 = " << str4 << endl;
}
int main()
{
/* code */
string str1 = "Hello ", str2 = "World", str3_for_func_1 = "!",
str3_for_func_2 = "!", str4 = "abc";
auto question_1 = curried(func_1); // For the first question
auto question_2 = curried(func_2); // For the second question
auto question_1_two_params = question_1(std::cref(str1))(std::ref(str2));
auto question_2_two_params = question_2(std::cref(str1))(std::ref(str2));
cout << "result : ";
//question_1_two_params(std::move(str3_for_func_1)); // Compile Error
//question_1_two_params(string("abc")); // Compile Error
//auto question_2_three_params = question_2_two_params(str3_for_func_2); // Compile Error
//question_2_three_params(std::ref(str4)); // It should output some log like "result : str1 = Hello, balabala..."
return 0;
}
编译命令:
$ g++ curryied.cc -std=c++17 -o curried
我的工作环境是:
OS : Ubuntu-20.04
Compiler : gcc version 9.3.0
问题 1
一个问题是在 std::apply(m_function, all_args);
行,您将 all_args
作为左值传递给 std::apply
,这将把它作为左值传递给 func_1
's第三个参数,这将失败,因为 func_1
的第三个参数是右值引用,它不能绑定到左值参数。
确实,将该行更改为 std::apply(m_function, std::move(all_args));
会使前两行 // Compile Error
实际编译并生成正确的输出。同样,对于 all_args
.
的其他用法,我也会调用 std::move
问题 2
看起来 std::make_tuple(std::forward<NewArgs>(args)...);
并没有按照您的想法行事。改成std::tuple<NewArgs&&...>(std::forward<NewArgs>(args)...);
即可解决问题;相当于 std::forward_as_tuple(std::forward<NewArgs>(args)...);
.
此更改起作用的细节在于 std::make_tuple
vs. std::forward_as_tuple
的 return 类型:后者 return 是一个引用元组,而前者 return 是一个元组来自参数的 copied/moved 的值。
现在,按照我的推理:
- 首先,看一下
curried(Function function, std::tuple<CapturedArgs...> args)
:它接受的参数args
应该是std::tuple<CaptureArgs...>
类型。我们确定 args
有那种类型吗?好吧,如果模板类型推导发生了,很明显答案是肯定的。但是,对该构造函数 never 的调用利用了类型推导,因为唯一的调用是在 return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
中,其中模板参数是 explicitly 提供。
- 所以问题仍然存在:
all_args
是构造器期望的类型吗?那么递归调用中的模板参数CapturedArgs..., NewArgs...
对应的是class的class... CapturedArgs
模板参数,用来构成构造函数的参数类型,std::tuple<CaptureArgs...>
.
- 所以这个问题的答案由
static_assert
ing 给出,在递归 return
之前,all_args
的类型是 std::tuple<CapturedArgs..., NewArgs...>
:
static_assert(std::is_same_v<decltype(all_args), std::tuple<CapturedArgs..., NewArgs...>>);
return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
- 不幸的是,你不能把这个断言放在代码中,只要你传递包装在
std::ref
/std::cref
中的值,因为那些失败了 static_assert
ion 但仍然是有效输入,正是因为 std::reference_wrapper
的工作方式。你可以写一个更复杂的断言,或者你可以暂时将 std::ref(bla)
更改为 bla
等等,并检查我给你的 static_assert
在使用 std::forward_as_tuple
时是否通过,而在使用 std::forward_as_tuple
时失败使用 std::make_tuple
.
感谢您提出这个问题。这是一个很好的机会让我再次深入这个复杂的话题并最终理解它!
再加一分
上面我建议你使用std::forward_as_tuple(std::forward<NewArgs>(args)...);
.
嗯,这个建议可能是错误的。
在第 238 页,作者明确指出他希望元组存储副本,以防止柯里化函数在其参数中幸存下来的情况。因此,最好改为使用它(注意,传递给 std::tuple
的模板参数中没有 &&
):
auto new_args = std::tuple<NewArgs...>(std::forward<NewArgs>(args)...);
我尝试编写一些代码来实现 C++17 中的柯里化函数。我的当前实现在下面(我将在这个问题的底部给你一个最小的工作示例)。
template <class Function, class... CapturedArgs>
class curried{
private:
using CapturedArgsTuple = std::tuple<std::decay_t<CapturedArgs>...>;
template <class... Args>
static auto capture_by_value(Args&&... args){
return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
}
public:
curried(Function function, CapturedArgs&&... args)
: m_function(function), m_capture(capture_by_value(std::move(args)...)){}
curried(Function function, std::tuple<CapturedArgs...> args)
: m_function(function), m_capture(std::move(args)){}
template <class... NewArgs>
auto operator()(NewArgs&&... args){
auto new_args = capture_by_value(std::forward<NewArgs>(args)...);
auto all_args = std::tuple_cat(m_capture, new_args);
if constexpr(std::is_invocable_v<Function, CapturedArgs..., NewArgs...>){
return std::apply(m_function, all_args);
}else{
return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
}
}
private:
Function m_function;
std::tuple<CapturedArgs...> m_capture;
};
这是一个测试函数:
void func(const string& str1, string& str2, string str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!";
auto test = curried(func);
auto test_two = test(std::cref(str1))(std::ref(str2));
cout << "result : ";
test_two(str3);
}
到目前为止一切顺利。我可以在我的终端上看到一些日志打印,例如:
$ result : str1 = Hello , str2 = Worldstr2 , str3 = !
我在这里有两个问题:
第一个是如何通过传递右值引用来调用柯里化函数?我已经尝试了所有可以搜索的方法,但结果要么是编译错误或者什么都没有。
void func_1(const string& str1, string& str2, string&& str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!";
auto test = curried(func_1);
auto test_two = test(std::cref(str1))(std::ref(str2));
cout << "result : ";
// test_two(std::move(str3)); Compile Error
// test_two(string("!")); Compile Error
test_two(std::bind(std::move<string&>, str3)); // Compile successfully, but there's nothing output
}
在解决第一个问题的过程中,我发现了一些奇怪的事情。这是一个例子:
void func_2(const string& str1, string& str2, string str3, string& str4){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3
<< ", str4 = " << str4 << endl;
}
int main(){
string str1 = "Hello ", str2 = "World", str3 = "!", str4 = "abc";
auto test = curried(func_2);
auto test_two = test(std::cref(str1))(std::ref(str2))(str3);
cout << "result : ";
test_two(std::ref(str4));
}
当我使用 func_2 测试我的柯里化函数时,我收到一些错误消息:
$ g++ curried.cc -std=c++17
curried.cc: In instantiation of ‘auto curried<Function, CapturedArgs>::operator()(NewArgs&& ...) [with NewArgs = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}; Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >}]’:
curried.cc:60:15: required from here
curried.cc:28:11: error: no matching function for call to ‘curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(void (*&)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)’
28 | return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
curried.cc:18:2: note: candidate: ‘curried<Function, CapturedArgs>::curried(Function, std::tuple<_Elements ...>) [with Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}]’
18 | curried(Function function, std::tuple<CapturedArgs...> args)
| ^~~~~~~
curried.cc:18:57: note: no known conversion for argument 2 from ‘tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>’ to ‘tuple<std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>’
18 | curried(Function function, std::tuple<CapturedArgs...> args)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
curried.cc:15:2: note: candidate: ‘curried<Function, CapturedArgs>::curried(Function, CapturedArgs&& ...) [with Function = void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&); CapturedArgs = {std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&}]’
15 | curried(Function function, CapturedArgs&&... args)
| ^~~~~~~
curried.cc:15:2: note: candidate expects 4 arguments, 2 provided
curried.cc:7:7: note: candidate: ‘constexpr curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(const curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&)’
7 | class curried{
| ^~~~~~~
curried.cc:7:7: note: candidate expects 1 argument, 2 provided
curried.cc:7:7: note: candidate: ‘constexpr curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>::curried(curried<void (*)(const std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char>&), std::reference_wrapper<const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::reference_wrapper<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&)’
curried.cc:7:7: note: candidate expects 1 argument, 2 provided
curried.cc: In function ‘int main()’:
curried.cc:60:10: error: void value not ignored as it ought to be
60 | test_two(str3)(std::ref(str4));
| ~~~~~~~~^~~~~~
所以第二个问题是为什么我不能把字符串参数str3
放在字符串引用前str4
? 为什么参数的顺序定义问题让我完全不知所措。
为方便起见,这里有一个最小的工作示例:
#include <iostream>
#include <functional>
#include <tuple>
using namespace std;
template <class Function, class... CapturedArgs>
class curried{
private:
using CapturedArgsTuple = std::tuple<std::decay_t<CapturedArgs>...>;
template <class... Args>
static auto capture_by_value(Args&&... args){
return std::tuple<std::decay_t<Args>...>(std::forward<Args>(args)...);
}
public:
curried(Function function, CapturedArgs&&... args)
: m_function(function), m_capture(capture_by_value(std::move(args)...)){}
curried(Function function, std::tuple<CapturedArgs...> args)
: m_function(function), m_capture(std::move(args)){}
template <class... NewArgs>
auto operator()(NewArgs&&... args){
auto new_args = std::make_tuple(std::forward<NewArgs>(args)...);
auto all_args = std::tuple_cat(m_capture, std::move(new_args));
if constexpr(std::is_invocable_v<Function, CapturedArgs..., NewArgs...>){
return std::apply(m_function, all_args);
}else{
return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
}
}
private:
Function m_function;
std::tuple<CapturedArgs...> m_capture;
};
void func_1(const string& str1, string& str2, string&& str3){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3 << endl;
}
void func_2(const string& str1, string& str2, string str3, string& str4){
str2 += "str2 ";
cout << "str1 = " << str1
<< ", str2 = " << str2
<< ", str3 = " << str3
<< ", str4 = " << str4 << endl;
}
int main()
{
/* code */
string str1 = "Hello ", str2 = "World", str3_for_func_1 = "!",
str3_for_func_2 = "!", str4 = "abc";
auto question_1 = curried(func_1); // For the first question
auto question_2 = curried(func_2); // For the second question
auto question_1_two_params = question_1(std::cref(str1))(std::ref(str2));
auto question_2_two_params = question_2(std::cref(str1))(std::ref(str2));
cout << "result : ";
//question_1_two_params(std::move(str3_for_func_1)); // Compile Error
//question_1_two_params(string("abc")); // Compile Error
//auto question_2_three_params = question_2_two_params(str3_for_func_2); // Compile Error
//question_2_three_params(std::ref(str4)); // It should output some log like "result : str1 = Hello, balabala..."
return 0;
}
编译命令:
$ g++ curryied.cc -std=c++17 -o curried
我的工作环境是:
OS : Ubuntu-20.04 Compiler : gcc version 9.3.0
问题 1
一个问题是在 std::apply(m_function, all_args);
行,您将 all_args
作为左值传递给 std::apply
,这将把它作为左值传递给 func_1
's第三个参数,这将失败,因为 func_1
的第三个参数是右值引用,它不能绑定到左值参数。
确实,将该行更改为 std::apply(m_function, std::move(all_args));
会使前两行 // Compile Error
实际编译并生成正确的输出。同样,对于 all_args
.
std::move
问题 2
看起来 std::make_tuple(std::forward<NewArgs>(args)...);
并没有按照您的想法行事。改成std::tuple<NewArgs&&...>(std::forward<NewArgs>(args)...);
即可解决问题;相当于 std::forward_as_tuple(std::forward<NewArgs>(args)...);
.
此更改起作用的细节在于 std::make_tuple
vs. std::forward_as_tuple
的 return 类型:后者 return 是一个引用元组,而前者 return 是一个元组来自参数的 copied/moved 的值。
现在,按照我的推理:
- 首先,看一下
curried(Function function, std::tuple<CapturedArgs...> args)
:它接受的参数args
应该是std::tuple<CaptureArgs...>
类型。我们确定args
有那种类型吗?好吧,如果模板类型推导发生了,很明显答案是肯定的。但是,对该构造函数 never 的调用利用了类型推导,因为唯一的调用是在return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
中,其中模板参数是 explicitly 提供。 - 所以问题仍然存在:
all_args
是构造器期望的类型吗?那么递归调用中的模板参数CapturedArgs..., NewArgs...
对应的是class的class... CapturedArgs
模板参数,用来构成构造函数的参数类型,std::tuple<CaptureArgs...>
. - 所以这个问题的答案由
static_assert
ing 给出,在递归return
之前,all_args
的类型是std::tuple<CapturedArgs..., NewArgs...>
:static_assert(std::is_same_v<decltype(all_args), std::tuple<CapturedArgs..., NewArgs...>>); return curried<Function, CapturedArgs..., NewArgs...>(m_function, all_args);
- 不幸的是,你不能把这个断言放在代码中,只要你传递包装在
std::ref
/std::cref
中的值,因为那些失败了static_assert
ion 但仍然是有效输入,正是因为std::reference_wrapper
的工作方式。你可以写一个更复杂的断言,或者你可以暂时将std::ref(bla)
更改为bla
等等,并检查我给你的static_assert
在使用std::forward_as_tuple
时是否通过,而在使用std::forward_as_tuple
时失败使用std::make_tuple
.
感谢您提出这个问题。这是一个很好的机会让我再次深入这个复杂的话题并最终理解它!
再加一分
上面我建议你使用std::forward_as_tuple(std::forward<NewArgs>(args)...);
.
嗯,这个建议可能是错误的。
在第 238 页,作者明确指出他希望元组存储副本,以防止柯里化函数在其参数中幸存下来的情况。因此,最好改为使用它(注意,传递给 std::tuple
的模板参数中没有 &&
):
auto new_args = std::tuple<NewArgs...>(std::forward<NewArgs>(args)...);