滥用逗号运算符来库里函数参数

Abusing comma operator to curry function arguments

本文关键字:函数 参数 运算符      更新时间:2023-10-16

在某些语言中,调用函数时可以省略括号。

frobnicate foo, bar, baz, qux

让我们看看我们是否可以将其或类似的东西带入C++。这几乎肯定意味着滥用逗号运算符,但是,据我所知,没有办法扩展任意类型以使用重载的逗号运算符。因此,像代理咖喱类型这样的东西可以工作。

comma_curry(frobnicate), foo, bar, baz, qux;

comma_curry(我不擅长想出名字)可能是某种增量库里尔,其逗号运算符一次接受一个参数。理想情况下,我希望它具有完美的转发语义,这样它与简单地直接调用frobnicate没有什么不同。

frobnicate(foo, bar, baz, qux);

特别是如果某些参数本身是函数调用,则可能会返回 prvalues。

frobnicate(foo(), bar, baz(), qux);

我已经尝试并提出了一个不起作用的示例,使用从另一篇文章复制的一些帮助程序代码。 有人有一些好或更好的想法吗?

当然,所有这些对于普通函数参数来说都是微不足道的。

我不建议使用逗号运算符,除非用于学习和玩具示例。不要将其放在生产代码中。

但是使用逗号运算符对于增量和重新库里也很有趣。下面是一个工作示例:

template<typename F, typename... Curry>
struct comma_curry {
template<typename... C>
explicit comma_curry(F function, C&&... c) noexcept :
function(std::move(function)), curry{std::forward<C>(c)...} {}
template<typename T>
friend auto operator,(comma_curry&& self, T&& arg) -> comma_curry<F, Curry..., std::decay_t<T>> {
return std::apply([&](auto&&... curry) {
return comma_curry<F, Curry..., std::decay_t<T>>(std::move(self.function), std::forward<decltype(curry)>(curry)..., std::forward<T>(arg));
}, std::move(self.curry));
}
template<typename... Args>
auto operator()(Args&&... args) const& -> decltype(auto) {
return std::apply([&](auto&&... curry) -> decltype(auto) {
return function(std::forward<decltype(curry)>(curry)..., std::forward<Args>(args)...);
}, curry);
}
template<typename... Args>
auto operator()(Args&&... args) && -> decltype(auto) {
return std::apply([&](auto&&... curry) -> decltype(auto) {
return std::move(function)(std::forward<decltype(curry)>(curry)..., std::forward<Args>(args)...);
}, std::move(curry));
}
private:
// [[no_unique_address]]
F function;
// [[no_unique_address]]
std::tuple<Curry...> curry;
};

它支持通过std::ref的柯里引用,并支持柯里仅移动类型。

它可以像这样使用:

int main() {
auto function = [](int& i, double, std::unique_ptr<int>, std::tuple<int>) {
std::cout << "Called! i value: " << i << std::endl;
};
int number = 1;
// Reference arguments are sent using std::ref
auto curried = (comma_curry(function), std::ref(number), 1.5);
curried(std::make_unique<int>(), std::tuple{1});
number = 42;
auto recurried = (std::move(curried), std::make_unique<int>(), std::tuple{1});
// We curried a std::unique_ptr, our function is a one time call
// Since it's a one time call and destroy itself in the calling,
// it must be moved
std::move(recurried)();
}

现场示例

这是我的想法的要点。请注意,它仅适用于单参数函数,但可以轻松扩展为咖喱。我使用标签类型来确保选择正确的重载:

#include <functional>
#include <type_traits>
#include <utility>
void print_thing(int a) {
}
struct go_t{} go; 
template<class F>
auto operator,(go_t, F f) {
return [a=std::move(f)](const auto& b) {
return a(b);
};
}
template<class T>
auto operator,(const std::function<void(T)>& f, T&& t) {
return f(std::forward<T>(t));
}
int main() {
// Make this happen!
(go, print_thing, 42);
}