在 C++20 中仍然无法转发所有可调用对象吗?

Is forwarding of all callables still not possible in C++20?

我想做一个可以接受任何东西的通用包装函数

包括重载或模板化 情况以及可变参数。然后,这样的包装器将在主体中使用转发的参数准确调用函数。

示例:

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()));

尽管这有点笨拙。