在C++中链接可调用对象

ryoqjall  于 2023-01-10  发布在  其他
关注(0)|答案(3)|浏览(156)

我来自python的世界,在那里我可以定义一系列操作,并在for循环中调用它们:

class AddOne:
    def __call__(self, x, **common_kwargs):
        return x+1
class Stringify:
    def __call__(self, x, **common_kwargs):
        return str(x)
class WrapNicely:
    def __call__(self, s, **common_kwargs):
        return "result="+s
data = 42
for operation in [AddOne(), Stringify(), WrapNicely()]:
    data = operation(data)
output = data

(Note:目标是进行复杂的操作。理想情况下,可以给出共同的kwargs)
如果每次调用后的返回类型可以不同,那么C++中的等价物是什么?
我不确定我能找到任何接近,但我可能有错误的关键字搜索...

dohp0rv5

dohp0rv51#

C++是静态类型的,所以这里的选项是有限的:

  • 创建可在编译时确定的函数链。
  • 创建参数和返回类型相同的函数
  • 返回可以“存储多个可选类型”的类型,如std::variant

对于第一种选择,你可以创建一个类模板,通过递归调用来执行函数,但是它比你的python代码要复杂一些:

template<class...Fs>
class Functions
{
    std::tuple<Fs...> m_functions;

    template<size_t index, class Arg>
    decltype(auto) CallHelper(Arg&& arg)
    {
        if constexpr (index == 0)
        {
            return std::forward<Arg>(arg);
        }
        else
        {
            return std::get<index - 1>(m_functions)(CallHelper<index - 1>(std::forward<Arg>(arg)));
        }
    }

public:
    Functions(Fs...functions)
        : m_functions(functions...)
    {
    }

    template<class Arg>
    decltype(auto) operator()(Arg&& arg)
    {
        return CallHelper<sizeof...(Fs)>(std::forward<Arg>(arg));
    }
};

int main() {
    Functions f{
        [](int x) { return x + 1; },
        [](int x) { return std::to_string(x); },
        [](std::string const& s) { return "result=" + s; }
    };

    std::cout << f(42) << '\n';
}

注意:这需要使用至少C17的C标准。

k4emjkb1

k4emjkb12#

靶区; DR

使用范围内的成分:

using std::views::transform;

auto fgh = transform(h) | transform(g) | transform(f);
auto fgh_x = std::array{42} | fgh; // Calculate f(g(h(x)))
// single element range ^^
//                      ^^ ranges::single_view{42} is an alternative

std::cout << fgh_x[0]; // Result is the only element in the array.

Demo(使用简单)
Demo(创建一个composer抽象)
为了更好地理解这在C中所带来的挑战,试着用type hints重新编写Python示例。尽管在一般情况下似乎"不可能"解决这个问题,但在C中,你可以用很多方法来解决这个问题。函数组合,就是你在这里使用它将最左边的操作的结果提供给右边的操作的方法。是一个"左折"运算符,如果您愿意冒险进入函数式编程领域,编写以下内容是可行的:

auto cfs = compose(f1, f2, f3);
std::cout << cfs(2, 3) << std::endl;

也就是说,即使有不同数量的输入,只要调用链可以组成,几年前我已经写了一系列关于这个主题的文章,可能this是最接近这个主题的。
也就是说,你也可以避免"函数的细微差别",使用一些很酷的C++20特性从头开始。下面是一个通用函数编写器的核心部分:

template <class... F>
auto composer(F&&... args)
{
    return [...functions = args](auto x)
    {
        return recurse_invoke(x, functions...);
    };
}

你可以用它来表示

// Store the composed function or call it right away.
composer(lambda1, lambda2, lambda3)(42);

编写器使用了一个C++20的变量捕获子句,这允许它管理任意长度的链。当然,一个适当的实现应该处理参数转发,如下所示
Demo
一个不需要辅助函数的小变化是(这里使用perf-forwarding处理):

template <class F, class... Fs>
auto composer(F&& arg, Fs&&... args)
{
    return [fun = std::forward<F>(arg), 
            ...functions = std::forward<Fs>(args)]<class X>(X&& x) mutable {
        if constexpr (sizeof...(Fs))
        {
            return composer(std::forward<Fs>(functions)...)(
                std::invoke(std::forward<F>(fun), std::forward<X>(x)));
        }
        else
        {
            return std::invoke(std::forward<F>(fun), std::forward<X>(x));
        }
    };
}

Demo

8yoxcaq7

8yoxcaq73#

在向python开发人员教授C时,您必须小心谨慎,以克服“C太复杂”的偏见。
在这方面,您有两种选择:

  • 如果你想链接操作,你可以直接嵌套lambdas,就像在python中一样,只是语法不同,请看我下面的回答。
  • 但是,如果您经常使用链接,并且希望应用线性compose(f,g,h)语法(这样可以保存您键入几个字符的时间),您应该自己生成一个composer。其他答案遵循此路径,为了简洁起见,我建议使用@NikosAthanasiou。

所以,这是一个简短的版本:给定某个变量x,并假设它是一个数字(当您应用+1时),您可以直接链接lambda:

auto operation = [](auto x) { return [](auto y) { return "result="+std::to_string(y); }(x+1); };

并将其用作

std::vector<int> v;  // -> fill the vector v 
std::vector<std::string> w;
for(auto& x : v)
{
    w.push_back(operation(x));
}

唯一遗漏的是从int到string的原地转换。对于这个,请参阅使用std::variant的其他答案,但为什么要使用它呢?...只有在真正需要时才使用它。

相关问题