如何通过重载 | 来链接和序列化函数操作员

How to chain and serialize functions by overloading the | operator

我正在尝试弄清楚如何为给定的基础 class 对象一般重载 operator|() 以序列化或链接类似于 pipesoperator<<() 有效...我想通过管道运算符将它们链接起来...这样我就可以拥有一系列独立的函数,并在单个数据对象上调用它们...换句话说,执行对同一数据类型进行多次转换,例如在流式系统中...

考虑以下伪代码示例: 这段代码可能无法编译,我手头没有编译器,而且我可能使用了错误的函数指针语法或函数对象作为运算符中的参数……这只是为了说明模式和行为我在追。

template<typename T>
typedef T(*Func)(T); // Function Pointer for functors-lambdas-etc... 

template<typename T>
struct pipe_object {
    T operator|(T(*Func)(T) func) {
        return func(T);
    }

    T operator()(T(*Func)(T) func) {
        return this->operator|(t, func);
    }
};

那么我可能想像这样使用它们:

constexpr int add_one_f(int x) {
    return (x+1);
}

constexpr int add_two_f(int x) {
   return (x+2);
}


void foo() {
    pipe_object<int> p1 = {};
    pipe_object<int> p2 = {};

    int result = p1(&add_one) | p2(&add_two); 

    // or something like...

    int result = p1 | p2; // ... etc ...

    // or something like:
    p1 = add_one | add_two | p2; // ... etc ...
}

我只是不知道如何在 |() 运算符中传播 intput - output...我是否必须重载两个版本以便它可以识别 |(lhs, rhs) 以及 |(rhs, lhs)?

不仅如此,如果我想扩展它以便我的 functorslambdas 接受多个参数怎么办...

我一直在对此进行 Google 搜索,只找到了一些资源,但没有找到任何具体、简单、优雅且至少与 C++17 功能保持同步的资源...

如果您知道关于这个主题的任何好的原始资料,请告诉我!

对于您在评论中添加的矢量示例,您可以使用如下语法

MyVec vec = {1, 2};
auto vec2 = vec | rotate(90) | scale(2.0) | translate(1.0,2.0);

这将按照以下逻辑工作:

class Transform {
public:
  virtual ~Transform () = default;
  virtual MyVector apply (const MyVector& in) const = 0;
};
inline MyVector operator| (const MyVector& v, const Transform& t) {
   return t.apply(v);
}

class Rotation : public Transform {
public:
  Rotation (int deg): m_deg(deg) {}
  MyVector apply (const MyVector& v) override {...}
private:
  int m_deg;
}:
Rotation rotate(int deg) { return Rotation(deg); }

以及缩放运算符和平移运算符的类似内容。

请注意 | 是从左到右关联的,因此输入必须在左侧馈送。但是您可能会考虑为每个采用转换参数和输入向量的函数添加一个重载,这样您就可以:

auto vec2 = rotate(90,vec) | scale(2.0) | translate(1.0,2.0);

后一种语法可能比使用恒等运算符开始管道更直观。

现在我可以使用我的编译器,并且在我的项目工作了一点之后再次使用;这就是我能够想出的...

它并不完美,因为 operator()function pointer 需要特别 2 个参数,如示例代码所示...

(我可能必须为此使用可变参数模板以及函数指针的签名以允许函数指针、函数对象、仿函数或 lambda 表达式与任何可接受的不同类型的参数数量...)

这是我的工作源代码...

#pragma once    

#include <exception>
#include <iostream>
#include <memory>

template<typename T>
class pipe;

template<typename T>
class pipe_object {
private:
    T t_;
public:
    explicit pipe_object(T t) : t_{ t } {}

    explicit pipe_object(pipe<T> pipe) : t_{ pipe.current()->value() } {}

    pipe_object(const pipe_object<T>& other) {
        this->t_ = other.t_;
    }

    pipe_object<T>& operator=(const pipe_object<T>& other) {
        this->t_ = other.t_;
        return *this;
    }

    T value() const { return t_; }

    T operator()() {
        return t_;
    }
};

template<typename T>
class pipe {
private:
    std::shared_ptr<pipe_object<T>> current_;
    std::shared_ptr<pipe_object<T>> next_;

public:
    explicit pipe(T t) : 
        current_{ nullptr },
        next_{ nullptr } 
    {
        current_.reset(new pipe_object<T>(t));
    }

    pipe_object<T>* current() const { return current_.get(); }
    pipe_object<T>* next() const { return next_.get(); }

    T operator|(pipe<T> in) {
        pipe_object<T>* temp = current_.get();
        next_.reset(new pipe_object<T>(in));
        current_ = next_;
        return temp->value();
    }

    T operator()(T a, T b, T(*Func)(T,T)) {
        return Func(a,b);
    }
};

constexpr int add(int a, int b) {
    return a + b;
}

int main() {
    try {    
        pipe<int> p1(1);
        pipe<int> p2(3);

        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                int x = p1(i,j, &add) | p2(i,j, &add);
                std::cout << x << ' ';                
            }
            std::cout << '\n';
        }
        // Game game;      
        // game.run();      
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

这是它给我的输出:

0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10 11
3 4 5 6 7 8 9 10 11 12
4 5 6 7 8 9 10 11 12 13
5 6 7 8 9 10 11 12 13 14
6 7 8 9 10 11 12 13 14 15
7 8 9 10 11 12 13 14 15 16
8 9 10 11 12 13 14 15 16 17
9 10 11 12 13 14 15 16 17 18

operator|() 管道似乎适用于我的 pipe class... 我不得不使用 shared_ptrpipe_object当前实例和下一个实例... pipe_object 只是某种类型 T 的基本包装器。 pipe_object class 的唯一特殊功能是它的构造函数之一......它是采用 pipe 对象并使用它提取 pipe 的值的构造函数构造一个新的pipe_object。由于 opeator|() 是右手关联的性质,我不得不使用双指针...

正如您在双 for 循环中从上面的源代码中看到的那样...我正在使用 pipe 的对象 operator() 将值和地址传递给函数...我也可以 pipe 这些对象而无需调用它们的 operator()... 从这里开始的下一步将是应用这个概念,但要使其适用于任何类型的对象...除非我可以只使用这些 classes 作为包装器来使用管道链技术!我的意思是让我的其他 class 从这个 pipe class 继承,就像使用 CRTP.

一样

告诉我你的想法!

首先我假设你有一些像这样的基础知识

#include <iostream>
struct vec2 {
    double x;
    double y;
};
std::ostream& operator<<(std::ostream& stream, vec2 v2) {return stream<<v2.x<<','<<v2.y;}

//real methods
vec2 translate(vec2 in, double a) {return vec2{in.x+a, in.y+a};} //dummy placeholder implementations
vec2 rotate(vec2 in, double a) {return vec2{in.x+1, in.y-1};}
vec2 scale(vec2 in, double a) {return vec2{in.x*a, in.y*a};}

因此,您需要的是用于操作的代理 class,其中使用函数和“其他参数”构造代理对象。 (我把函数做成了模板参数,这样就避免了函数指针的使用,帮助优化器内联,使得这个开销几乎为零。)

#include <type_traits>
//operation proxy class
template<class rhst, //type of the only parameter
     vec2(*f)(vec2,rhst)> //the function to call
class vec2_op1 {
    std::decay_t<rhst> rhs; //store the parameter until the call
public:
    vec2_op1(rhst rhs_) : rhs(std::forward<rhst>(rhs_)) {}
    vec2 operator()(vec2 lhs) {return f(lhs, std::forward<rhst>(rhs));}
};

//proxy methods
vec2_op1<double,translate> translate(double a) {return {a};}
vec2_op1<double,rotate> rotate(double a) {return {a};}
vec2_op1<double,scale> scale(double a) {return {a};}

然后您只需将其设为可链接

//lhs is a vec2, rhs is a vec2_operation to use
template<class rhst, vec2(*f)(vec2,rhst)>
vec2& operator|(vec2& lhs, vec2_op1<rhst, f>&& op) {return lhs=op(lhs);}

用法很简单:

int main() {
    vec2 v2{3,5};
    v2 | translate(2.5) | rotate(30) | translate(3) | scale(2);
    std::cout << v2;
}

http://coliru.stacked-crooked.com/a/9b58992b36ff12d3

注意:没有分配,没有指针,没有复制或移动。这应该会生成与您刚刚生成的代码相同的代码 v2.translate(2.5); v2.rotate(30); v2.scale(10); 直接。