-3

I'm trying to figure out how to generically overload the operator|() for a given base class object to serialize or chain function calls that are similar to how pipes or operator<<() works... I'd like to chain them through the pipe operator... This way I can have a series of standalone functions, and call them on a single data object... In other words, to perform multiple transformations on the same data type, like in a streaming system...

Consider the following pseudo code sample: this code probably won't compile, I don't have my compiler handy and I may be using the wrong syntax for the function pointers or function objects as a parameter in the operators... This is only to illustrate the pattern and behavior that I'm after.

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

Then I might want to use them something like this:

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 ...
}

I just don't know how to propagate the intput - output in the |() operator... Would I have to overload two versions so that it can recognize |(lhs, rhs) as well as |(rhs, lhs)?

More than just that, what if I want to expand this so that my functors or lambdas were to take multiple arguments...

I've been doing Google searches on this and only found a couple of resources but nothing that is concrete, simple, elegant, and up to date at least with C++17 features...

If you know of any good source materials on this subject please let me know!

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • @IgorTandetnik I know, it's just pseudo code... I don't have my compiler handy atm... But the intention is to take a functor like object... or maybe a value and a functor... – Francis Cugler Jul 22 '20 at 02:26
  • 2
    None of your usage examples make much sense to me. What is the value of `result` supposed to be in the end? What do you add one or two **to**? What role are `p1` and `p2` supposed to play? – Igor Tandetnik Jul 22 '20 at 02:32
  • @Ignor consider an object like a 2D vector... let's say it is already populated with values... such as `vec2 v2 = {3,5}`... then I'd like to be able to do something like: `v2 = rotate(30) | scale(5) | translate(15);` then it would rotate it 30 degrees or radians, scale it by 5 units and then translate by 15... Almost kind of how `linux's pipes` work... – Francis Cugler Jul 22 '20 at 02:34
  • Do you control the definition of `vec2`? Can you give it an assignment operator that would accept an [expression template](https://en.wikipedia.org/wiki/Expression_templates) object representing this sequence of transformations? – Igor Tandetnik Jul 22 '20 at 02:36
  • @IgorTandetnik These would be my custom classes, so yes... I already have a `Component` class hierarchy that uses the `CRTP` design and this is an operator that I'd like to be able to add to the base class so that all component classes can use this operating in a changing fashion... The operator can live outside of the class and just take in the objects or it can be defined within... either method works for me... – Francis Cugler Jul 22 '20 at 02:37
  • 2
    Then a) you probably want to put your actual motivating example in the question, because what you have there now doesn't make sense, and b) like I said, the technique you are looking for is called "expression templates". You should find some examples if you search for that. – Igor Tandetnik Jul 22 '20 at 02:40
  • @Igor Yeah I'm familiar with the CRTP... and I was doing searches on `operator|()` overloading, pipelining, chaining, etc... I'll have to look into the `expression templates`... Now, are there any new features within c++17 that can be utilized to make this simpler? I don't have a C++20 compiler yet to utilize `concepts`, `ranges` etc.. – Francis Cugler Jul 22 '20 at 02:47
  • @IgorTandetnik If found this link, it has a somewhat useful example... http://pfultz2.com/blog/2014/09/05/pipable-functions/ that is similar to what I'm after... But I think it is adding too much complexity... I have one base class `Component` and from that, I might have 4 abstract types, and each of them can have 3-10 variants... It's when you begin to template everything to make the code generic that begins to get a little bit confusing... – Francis Cugler Jul 22 '20 at 02:51
  • @IgorTandetnik I've provided my own answer now that my compiler - IDE is available to me. I think from the answer, you can see what I was trying to get at... Let me know what you think by leaving a comment under my answer. – Francis Cugler Jul 23 '20 at 01:23
  • @MooingDuck Yes, that's what I was trying to mimic but through the use of the `opeator|()` since it is rarely overloaded... I don't want to use the `<<` or `>>` operators since I will be using them for `input` and `output` of my classes! And considering that `Linux` commands have a piping technique, I wanted to mimic that within my c++ source code since the `|` is commonly used to piping or chaining of commands. – Francis Cugler Jul 23 '20 at 01:28
  • @FrancisCugler: I removed my comment, because upon further reading, your question is baffling to me and I don't understand it and I'm not convinced its the same at all. `int result = p1(&add_one) | p2(&add_two);` What operation should the `|` be doing here? Nobody seems able to figure out what you mean to do with those two integers. – Mooing Duck Jul 23 '20 at 01:33
  • @MooingDuck It's kind of hard to put into words... but consider having a type that is already constructed such as `vec2 v2{3,5}` Then let's say I want to perform a series of translations on that vector... Then I'd have something like this: `v2 | translate(2.5) | rotate(30) | translate(3) | scale(2); Then it would translate that vector by 2.5 units, rotate it by 30 degrees or radians, then translate it by 3 units and then scale it by 2 in that order. It's a sequence of operations being done on a single data type! The vector was just a representation... – Francis Cugler Jul 23 '20 at 01:37
  • @FrancisCugler well that's easy, depending on if you can edit `transform` and `rotate` and such – Mooing Duck Jul 23 '20 at 01:38
  • @Mooing so instead of having code like `v2.translate(2.5); v2.rotate(30); v2.scale(10)` I want to use the `|` operator to perform those functions on that data pipe in one line chaining or piping the commands. – Francis Cugler Jul 23 '20 at 01:39
  • @MooingDuck Well I'm not using a `predefined library` it's my own project so all of my classes are my own... I have full control over their implementation and interfaces... I just want to know how to do this generically so that it can work for any of my objects without having to rewrite this operator for every class.... – Francis Cugler Jul 23 '20 at 01:41
  • @MooingDuck now with the two classes that I've shown below, I could have my other classes inherit from them in a CRTP fashion and that may allow me to have my other class objects have this property... – Francis Cugler Jul 23 '20 at 01:43

3 Answers3

1

First I assume you have some basics that look like this

#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};}

So what you want is a proxy class for operations, where a proxy object is constructed with the function and the "other parameters". (I made the function a template parameter, which prevents the use of function pointers, and helps the optimizer to inline, making this nearly zero overhead.)

#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};}

And then you simply make that chainable

//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);}

Usage is simple:

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

Note: No allocations, no pointers, no copies or moves. This should generate the same code as if you just did v2.translate(2.5); v2.rotate(30); v2.scale(10); directly.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Not 100% exactly as I was looking for, however, this technique can be very useful! I also like how you pointed out the "Zero Overhead" without the need of using function pointers... The only thing here is if I have a different set of classes I'd have to implement this for each set... However, once I become familiar with the pattern, it shouldn't be that hard to replicate! – Francis Cugler Jul 23 '20 at 01:58
  • 1
    Down the road once I have a fully compliant C++20 compiler (still on a C+17) compiler I should be able to utilize `concepts`, `ranges`, etc... to make this even a bit more trivial! – Francis Cugler Jul 23 '20 at 01:59
  • 1
    You can use SFINAE to extend a single implementation to any classes that match a set of constraints. Its also not all that hard to extend this to things where the return value is different. `myclass | classToString | stringToInteger;`. – Mooing Duck Jul 23 '20 at 01:59
  • @FrancisCugler: Another option is [`std::bind`](http://coliru.stacked-crooked.com/a/581c0b6b014e37de), which has less template magic that you write, and is more flexible, but also more verbose at the callsite – Mooing Duck Jul 23 '20 at 19:44
0

For your vector example you added in a comment, you could have a syntax like this

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

The way this would work is with the following logic:

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

and something similar for a scaling operator and a translation operator.

Notice that | is associative left-to-right, so the input has to be fed on the left. But you may think of adding an overload to each of those functions that take the transformation parameters and an input vector, so that you can do:

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

The latter syntax might be sligthly more intuitive than starting the piping with, basically, and identity operator.

bartgol
  • 1,703
  • 2
  • 20
  • 30
0

Now that I have my compiler available to me and again after working on my project for a little bit; This is what I was able to come up with...

It's not perfect since the function pointer for the operator() requires specifically 2 parameters as can be seen in this sample code...

(I'd probably have to use a variadic template for this as well as for the signature for the function pointer to allow for a function pointer, function object, functor, or lambda expression with any number of parameters of varying types to be acceptable...)

Here is my working source code...

#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;
}

And here is the output that it is giving me:

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

The operator|() pipe seems to be working for my pipe class... and I had to use a shared_ptr of a pipe_object for the current instance and the next instance... the pipe_object is just a basic wrapper around some type T. The only special feature of the pipe_object class is one of its constructors... It's the constructor that takes a pipe object and extracts that pipe's value using it to construct a new pipe_object. I had to use double pointers due to the nature of the opeator|() being right hand associative...

As you can see from the source code above within the double for loop... I'm using the pipe's objects operator() to pass in the values and an address to a function... I can also pipe these objects without calling their operator()... The next step from here would be to apply this concept but to make it general for any kind of object... unless I could just use these classes as a wrapper to use the pipe chaining technique! I mean by having my other class inherit from this pipe class like one would do using CRTP.

Let me know what you think!

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • Even after reading your code, I _still_ can't figure out what you want `operator|` to do. It seems to be "make the left equal to the right, and return what the left used to be". I'm fairly certain this can be drastically simplified however – Mooing Duck Jul 23 '20 at 01:35
  • @MooingDuck I want it to evaluate the function that is being applied from left to right and to apply that function as it is encountered... – Francis Cugler Jul 23 '20 at 01:38