NeutronStar
NeutronStar

Reputation: 230

Overloading bitwise OR('|') for chaining operations not working as expected

I am trying overload bitwise OR '|' operator so that I can chain different operations one after another.

I have implemented the following code.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

struct Test
{
    explicit Test(vector<int> vdata) : data_(vdata) { }

    vector<int>& operator()()
    {
        return data_;
    }

    template<typename Pred>
    Test operator | (Pred P)
    {
        *this = P;
        return *this;
    }

    vector<int> data_;
};

template <typename Con, typename Pred>
Con Transform(Con& C, Pred P)
{
    vector<int> res;

    transform(begin(C()), end(C()),back_inserter(res),  P);

    return Con(res);
}

template <typename Con, typename Pred>
Con Filter(Con& C, Pred P)
{
    vector<int> res;

    remove_copy_if(begin(C()), end(C()), back_inserter(res), P);

    return Con(res);
}

int main()
{
    vector<int> vdata{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

    auto DoubleIt = [](int& v) {
        return v *= 2;
    };

    auto Remove_Lteq4 = [](auto v) {
        return v <= 4;
    };

    auto Remove_divideby3=[](auto v) 
    {
        return !(v % 3);
    };

    Test test(vdata);

    Test test1 =  
     test | Filter(test, Remove_Lteq4) |  
     Transform(test, DoubleIt) | 
     Filter(test, Remove_divideby3);

    // output
    for (auto v : test1())
        cout << v << " ";            
}

The code is working fine giving expected output on Cxxdroid 2.0_arm offline compiler, C++14/C++17, but I am getting different results on online compilers

Input: vector = {1,2,3,4,5,6,7,8,9,10,11,12} Operations applied in sequence are Filter values <= 4 then multiply the remaining by 2 then filter values divisible by 3

Results offline compiler (Cxxdroid) expected output: 10, 14, 16, 20, 22 actual output: 10, 14, 16, 20, 22

Online compiler actual output: 1,2,4,5,7,8,10,11 (this is actually the output you would get if you only apply remove_divideby3 function to the input).

I am struggling with it for some time now and unable to figure out the reason for different output. Can someone please tell where I am making the mistake. The code works fine if I apply just one opetation at a time.

I will also be very greatful if someone could answer addition queries.

  1. Do I need to write copy constructor/copy assignment, move constructor and move assignment.

  2. Is my usage of objects correct in terms of passing and returning the objects by value and by reference. I think the issue is somewhere here, probabily passing an object where a reference is needed.

  3. Is there a need to overload '|' at global level.

Note: using namespace std; is used for convenience purpose only as I had typed the code on mobile.

Thank you

Upvotes: 1

Views: 84

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275350

template<class F>
struct pipe_func {
  F f;

  template<class Lhs>
  friend auto operator|( Lhs&& lhs, pipe_func rhs ) {
    return rhs.f( std::forward<Lhs>(lhs) );
  }
};
template<class F>
pipe_func(F)->pipe_func<F>;

there is a building block for you. A pipe_target takes a function, and when piped things feeds it to the function and returns the result.

Now suppose you want to write filter.

template<class Pred>
auto Filter( Pred p ) {
  return pipe_func{[p]( auto&& container ) {
    using std::begin; using std::end;
    using R = std::decay_t< decltype(container) >;
    R retval;
    std::remove_copy_if( begin(container), end(container), std::back_inserter(retval), p );
    return retval;
  }};
}

so filter is a function taking a predicate.

It returns a pipe_func. The pipe_func has a lambda in it that applies the predicate.

Transform is similar:

template<class Pred>
auto Transform( Pred p ) {
  return pipe_func{[p]( auto&& container ) {
    using std::begin; using std::end;
    using R = std::decay_t< decltype(container) >;
    R retval;
    std::transform( begin(container), end(container), std::back_inserter(retval), p );
    return retval;
  }};
}

Test code looks like:

std::vector<int> test1 =  
 vdata | Filter(Remove_Lteq4) |  
 Transform(DoubleIt) | 
 Filter(Remove_divideby3);

Live example.

Now, a solution where we can pipe into functions taking two arguments is a bit fishy; the function still thinks it takes 2 arguments, so passing it its second is tricky.

I'd avoid it. Any solution would be a hack.

Upvotes: 2

Barry
Barry

Reputation: 302807

You're doing this (I rearranged the pipes to make it easier to read):

Test test1 =  
 test | Filter(test, Remove_Lteq4)
      | Transform(test, DoubleIt)
      | Filter(test, Remove_divideby3);

Let's start from the back. What does Filter(test, Remove_divideby3) do? It gives back a container that is all the elements from test that satisfy Remove_divideby3. But test is what you started with, it doesn't depend on anything in any part of this operation. It doesn't matter what the other Filter or Transform return, since none of those values is used as input into this Filter.

That's why what you're getting is no different from having written:

Test test1 = Filter(test, Remove_divideby3);

Your adapters have either take the range as an argument:

Filter(Transform(Filter(test, Remove_Lteq4), DoubleIt), Remove_divideby3)

or use pipes:

test | Filter(Remove_Lteq4) | Transform(DoubleIt) | Filter(Remove_divideby3)

Not both at the same time.

Upvotes: 1

Related Questions