在 C++20 中仍然无法转发所有可调用对象吗?
Is forwarding of all callables still not possible in C++20?
我想做一个可以接受任何东西的通用包装函数
- 独立或静态功能,
- 成员函数(可能作为第一个参数用作
*this
的特例)
包括重载或模板化 情况以及可变参数。然后,这样的包装器将在主体中使用转发的参数准确调用函数。
示例:
template<typename Fnc,typename...Args>
void wrapper(Fnc fnc, Args&&...args){
// Do some stuff
// Please disregard the case when return type is void,
// that be SFINAED with std::result_of.
auto res = fnc(std::forward<Args>(args)...);
// Do some stuff
return res;
}
#include <vector>
auto foo(int i){ return i; }
auto foo(double& d){ return d; }
auto foo(double&& d){ return d; }
auto foo(const char* str){ return str; }
template<typename...T>
auto generic_foo(T&&...ts){/* ...*/ return /* ...*/; }
template<typename T>
void constrained_foo(std::vector<T>& lref,std::vector<T>&& rref, std::vector<T> value){ /**/}
int main(){
// Basics
wrapper(foo, 1);// foo(int)
wrapper(foo, 1.1); // foo(double&&)
wrapper(foo, "ahoj"); // foo(const char*)
// Conversion must work too
wrapper(foo, 1.1f); // foo(double&&)
wrapper(foo, (short)1); // foo(int)
// Detecting lvalues, rvalues is also a must
double y;
foo(y);
foo(std::move(y));
// Similarly for templates
int x;
std::vector<int> v1, v2, v3;
wrapper(generic_foo, 1, 1.1, "ahoj", x, &x);
wrapper(constrained_foo, v1, std::move(v2), v3);
}
让我感到非常沮丧的是,我提供了所有必要的信息来使这些调用彼此相邻,没有关于调用什么的额外歧义,我可以自己调用,我可以(将)制作一个可以做到这一点的宏,但是据我所知没有合适的 C++ 语法。
我在尝试在特定“上下文”中自动调用我的所有方法时发现了它的需要。但仔细想想,我相信这也可能在 <algorithm>
、<thread>
库中得到广泛使用。在这里,您不必仅使用 lambda 参数或捕获的内容调用 function/operator 来创建单语句 lambda。
问题出现在接受另一个函数的任何函数中,该函数最终将与传递的已知参数一起调用。
我的尝试
generic_foo
如果固定return类型就可以解决:
template<typename...Args>
void wrapper(void(*f)(Args&&... args) , Args&&...args){
// ...
f(std::forward<Args>(args)...);
}
int main(){
int x;
wrapper(generic_foo, 1, 1.1, "ahoj", x, &x, std::move(x));
}
这很好用,return 类型也可以通过一些晦涩而巧妙的 std::invoke_result_t
来解决,但目前它是参数类型的一种先有鸡还是先有蛋的情况。因为解析名称 generic_foo
的唯一方法是强制它衰减为函数指针,然后没有名称可放入 std::invoke_result_t
,因为参数仍在推导中。
只要存在完全匹配,这也适用于重载,因此它无法进行转换。
当事先不知道函数名称时,这种方法是我在没有宏的情况下所能得到的。
如果可调用对象的名称是固定的,那么有一个经常使用的 lambda 技巧变体:
template<typename Fnc, typename...Args>
void wrapper(Fnc f , Args&&...args){
// ...
f(std::forward<Args>(args)...);
}
int main(){
int x;
wrapper([](auto&&...args)
{ return generic_foo(std::forward<decltype(args)>(args)...);},
1, 1.1, "ahoj", x, &x, std::move(x));
}
如果我添加一个宏来执行此操作:
#define WRAP_CALL(wrapper_fnc, called_fnc, ...) \
wrapper_fnc([&](auto&&...args) \
{ return called_fnc(std::forward<decltype(args)>(args)...);}, \
__VA_ARGS__ );
int main(){
int x;
WRAP_CALL(wrapper, generic_foo, 1, 1.1, "ahoj", x, &x, std::move(x))
}
我得到了我能想到的宏感染最少的工作解决方案,它适用于任何可调用的和任何可以保留适当的 C++ 函数的包装器。但我想要这个的无宏版本,尤其是函数。
所以我大概还是会用这个版本,好像也不算太离谱。有什么我应该知道的角落案例吗?
我还没有写过一个 C++20 概念,所以我仍然抱有很小的希望,也许在那个领域可能会有一些东西可以工作?但可能不会,因为 std::thread(foo,1);
也患有此病。
所以这可能很遗憾地需要更改语言,因为重载集或模板的名称目前无法在任何地方传递,即使只是作为某种聚合类型。所以也许类似于 std::initializer_list
class 加上它有时神奇的语法?
如果情况确实如此,我很乐意接受任何列出可能对此有帮助的当前活动提案的任何答案。如果有的话。
我确实找到了 N3579 - A Type trait for signatures 如果解决了先有鸡还是先有蛋的问题,它或许可以与函数指针解决方案一起工作。但是提案看起来很死板。
“重载或模板化情况”不是 实体 可以是 function/template 参数——某些情况下重载决议可以使用上下文信息。 wrapper(foo,…)
中 foo
的意思只不过是 token (特别是,它是一个重载集),而语言根本无法解决这样一个对象,因为它没有(“聚合”?)类型。 (相反,宏 do 对标记进行操作,这就是它们适用于此处的原因。)您似乎了解大部分内容,但了解 why[ 可能会有所帮助=32=] 众所周知,这是不可能的,为什么将重载设置为“可调用”是个坏主意。 (毕竟,在句法上类型名称也是“可调用的”,但将其作为函数参数传递没有任何意义。)
即使调用 f(g,…)
被定义为依次尝试 g
的每个重载(实际上是为了推导 f
' s 模板参数),这对(包含模板的重载集)没有帮助:在给定 g
模板的情况下,甚至无法评估 f
的 SFINAE 条件尚未选择专业。
标准的 lambda 技巧,你也说明了,是一种利用参数列表执行重载决议的方法,这就是为什么它几乎是唯一的方法作品。有 certainly proposals 可以使该过程自动化,包括变幻莫测的 SFINAE 友好性和异常规范。
改为考虑这个:
template <class T>
auto wrapper(T fnc) {
// Do some stuff
auto res = fnc(); // <--- no arguments
// Do more stuff
return res;
}
由于参数在 wrapper
调用时已知,它们也可以在 wrapper
调用时绑定。
wrapper([](){ return foo(1); });
wrapper([](){ return foo(1.1); });
wrapper([](){ return foo("ahoj"); });
wrapper([&x](){ return generic_foo(1, 1.1, "ahoj", x, &x); });
你可以把它封装在一个宏中,这当然不太理想,但至少这个是简短易读的。
#define DEFER(x) ([&](){return x;})
wrapper(DEFER(foo(1)));
wrapper(DEFER(foo(1.1)));
wrapper(DEFER(foo("ahoj")));
wrapper(DEFER(generic_foo(1, 1.1, "ahoj", x, &x)));
这不完全你想要的,例如,在
wrapper(DEFER(foo(bar())))
bar
被称为迟到。这可以通过一些语法修复:
wrapper([z=bar()](){ return foo(z); });
当然这也可以用宏包装:
wrapper(DEFER1(foo(z), z=bar()));
尽管这有点笨拙。
我想做一个可以接受任何东西的通用包装函数
- 独立或静态功能,
- 成员函数(可能作为第一个参数用作
*this
的特例)
包括重载或模板化 情况以及可变参数。然后,这样的包装器将在主体中使用转发的参数准确调用函数。
示例:
template<typename Fnc,typename...Args>
void wrapper(Fnc fnc, Args&&...args){
// Do some stuff
// Please disregard the case when return type is void,
// that be SFINAED with std::result_of.
auto res = fnc(std::forward<Args>(args)...);
// Do some stuff
return res;
}
#include <vector>
auto foo(int i){ return i; }
auto foo(double& d){ return d; }
auto foo(double&& d){ return d; }
auto foo(const char* str){ return str; }
template<typename...T>
auto generic_foo(T&&...ts){/* ...*/ return /* ...*/; }
template<typename T>
void constrained_foo(std::vector<T>& lref,std::vector<T>&& rref, std::vector<T> value){ /**/}
int main(){
// Basics
wrapper(foo, 1);// foo(int)
wrapper(foo, 1.1); // foo(double&&)
wrapper(foo, "ahoj"); // foo(const char*)
// Conversion must work too
wrapper(foo, 1.1f); // foo(double&&)
wrapper(foo, (short)1); // foo(int)
// Detecting lvalues, rvalues is also a must
double y;
foo(y);
foo(std::move(y));
// Similarly for templates
int x;
std::vector<int> v1, v2, v3;
wrapper(generic_foo, 1, 1.1, "ahoj", x, &x);
wrapper(constrained_foo, v1, std::move(v2), v3);
}
让我感到非常沮丧的是,我提供了所有必要的信息来使这些调用彼此相邻,没有关于调用什么的额外歧义,我可以自己调用,我可以(将)制作一个可以做到这一点的宏,但是据我所知没有合适的 C++ 语法。
我在尝试在特定“上下文”中自动调用我的所有方法时发现了它的需要。但仔细想想,我相信这也可能在 <algorithm>
、<thread>
库中得到广泛使用。在这里,您不必仅使用 lambda 参数或捕获的内容调用 function/operator 来创建单语句 lambda。
问题出现在接受另一个函数的任何函数中,该函数最终将与传递的已知参数一起调用。
我的尝试
generic_foo
如果固定return类型就可以解决:
template<typename...Args>
void wrapper(void(*f)(Args&&... args) , Args&&...args){
// ...
f(std::forward<Args>(args)...);
}
int main(){
int x;
wrapper(generic_foo, 1, 1.1, "ahoj", x, &x, std::move(x));
}
这很好用,return 类型也可以通过一些晦涩而巧妙的 std::invoke_result_t
来解决,但目前它是参数类型的一种先有鸡还是先有蛋的情况。因为解析名称 generic_foo
的唯一方法是强制它衰减为函数指针,然后没有名称可放入 std::invoke_result_t
,因为参数仍在推导中。
只要存在完全匹配,这也适用于重载,因此它无法进行转换。 当事先不知道函数名称时,这种方法是我在没有宏的情况下所能得到的。
如果可调用对象的名称是固定的,那么有一个经常使用的 lambda 技巧变体:
template<typename Fnc, typename...Args>
void wrapper(Fnc f , Args&&...args){
// ...
f(std::forward<Args>(args)...);
}
int main(){
int x;
wrapper([](auto&&...args)
{ return generic_foo(std::forward<decltype(args)>(args)...);},
1, 1.1, "ahoj", x, &x, std::move(x));
}
如果我添加一个宏来执行此操作:
#define WRAP_CALL(wrapper_fnc, called_fnc, ...) \
wrapper_fnc([&](auto&&...args) \
{ return called_fnc(std::forward<decltype(args)>(args)...);}, \
__VA_ARGS__ );
int main(){
int x;
WRAP_CALL(wrapper, generic_foo, 1, 1.1, "ahoj", x, &x, std::move(x))
}
我得到了我能想到的宏感染最少的工作解决方案,它适用于任何可调用的和任何可以保留适当的 C++ 函数的包装器。但我想要这个的无宏版本,尤其是函数。
所以我大概还是会用这个版本,好像也不算太离谱。有什么我应该知道的角落案例吗?
我还没有写过一个 C++20 概念,所以我仍然抱有很小的希望,也许在那个领域可能会有一些东西可以工作?但可能不会,因为 std::thread(foo,1);
也患有此病。
所以这可能很遗憾地需要更改语言,因为重载集或模板的名称目前无法在任何地方传递,即使只是作为某种聚合类型。所以也许类似于 std::initializer_list
class 加上它有时神奇的语法?
如果情况确实如此,我很乐意接受任何列出可能对此有帮助的当前活动提案的任何答案。如果有的话。
我确实找到了 N3579 - A Type trait for signatures 如果解决了先有鸡还是先有蛋的问题,它或许可以与函数指针解决方案一起工作。但是提案看起来很死板。
“重载或模板化情况”不是 实体 可以是 function/template 参数——某些情况下重载决议可以使用上下文信息。 wrapper(foo,…)
中 foo
的意思只不过是 token (特别是,它是一个重载集),而语言根本无法解决这样一个对象,因为它没有(“聚合”?)类型。 (相反,宏 do 对标记进行操作,这就是它们适用于此处的原因。)您似乎了解大部分内容,但了解 why[ 可能会有所帮助=32=] 众所周知,这是不可能的,为什么将重载设置为“可调用”是个坏主意。 (毕竟,在句法上类型名称也是“可调用的”,但将其作为函数参数传递没有任何意义。)
即使调用 f(g,…)
被定义为依次尝试 g
的每个重载(实际上是为了推导 f
' s 模板参数),这对(包含模板的重载集)没有帮助:在给定 g
模板的情况下,甚至无法评估 f
的 SFINAE 条件尚未选择专业。
标准的 lambda 技巧,你也说明了,是一种利用参数列表执行重载决议的方法,这就是为什么它几乎是唯一的方法作品。有 certainly proposals 可以使该过程自动化,包括变幻莫测的 SFINAE 友好性和异常规范。
改为考虑这个:
template <class T>
auto wrapper(T fnc) {
// Do some stuff
auto res = fnc(); // <--- no arguments
// Do more stuff
return res;
}
由于参数在 wrapper
调用时已知,它们也可以在 wrapper
调用时绑定。
wrapper([](){ return foo(1); });
wrapper([](){ return foo(1.1); });
wrapper([](){ return foo("ahoj"); });
wrapper([&x](){ return generic_foo(1, 1.1, "ahoj", x, &x); });
你可以把它封装在一个宏中,这当然不太理想,但至少这个是简短易读的。
#define DEFER(x) ([&](){return x;})
wrapper(DEFER(foo(1)));
wrapper(DEFER(foo(1.1)));
wrapper(DEFER(foo("ahoj")));
wrapper(DEFER(generic_foo(1, 1.1, "ahoj", x, &x)));
这不完全你想要的,例如,在
wrapper(DEFER(foo(bar())))
bar
被称为迟到。这可以通过一些语法修复:
wrapper([z=bar()](){ return foo(z); });
当然这也可以用宏包装:
wrapper(DEFER1(foo(z), z=bar()));
尽管这有点笨拙。