//  extending_return_type_traits.cpp  --------------------------------

#define BOOST_INCLUDE_MAIN  // for testing, include rather than link
#include <boost/test/test_tools.hpp>    // see "Header Implementation Option"

#include "boost/lambda/bind.hpp"
#include "boost/lambda/lambda.hpp"

#include <iostream>

#include <functional>

#include <algorithm>

class A {};
class B {};

using namespace boost::lambda; 


B operator--(const A&, int) { return B(); }
B operator--(A&) { return B(); }
B operator++(const A&, int) { return B(); }
B operator++(A&) { return B(); }
B operator-(const A&) { return B(); }
B operator+(const A&) { return B(); }

B operator!(const A&) { return B(); }

B operator&(const A&) { return B(); }
B operator*(const A&) { return B(); }

namespace boost {
namespace lambda {

  // unary + and -
template<class Act>
struct plain_return_type_1<unary_arithmetic_action<Act>, A > {
  typedef B type;
};

  // post incr/decr
template<class Act>
struct plain_return_type_1<post_increment_decrement_action<Act>, A > {
  typedef B type;
};

  // pre incr/decr
template<class Act>
struct plain_return_type_1<pre_increment_decrement_action<Act>, A > {
  typedef B type;
};
  // !
template<> 
struct plain_return_type_1<logical_action<not_action>, A> {
  typedef B type;
};
  // &
template<> 
struct plain_return_type_1<other_action<addressof_action>, A> {
  typedef B type;
};
  // *
template<> 
struct plain_return_type_1<other_action<contentsof_action>, A> {
  typedef B type;
};


} // lambda
} // boost

void ok(B b) {}

void test_unary_operators() 
{
  A a; int i = 1;
  ok((++_1)(a));
  ok((--_1)(a));
  ok((_1++)(a));
  ok((_1--)(a));
  ok((+_1)(a));
  ok((-_1)(a));
  ok((!_1)(a));
  ok((&_1)(a));
  ok((*_1)(a));

  BOOST_TEST((*_1)(make_const(&i)) == 1);
}

class X {};
class Y {};
class Z {};

Z operator+(const X&, const Y&) { return Z(); }
Z operator-(const X&, const Y&) { return Z(); }
X operator*(const X&, const Y&) { return X(); }

Z operator/(const X&, const Y&) { return Z(); }
Z operator%(const X&, const Y&) { return Z(); }

class XX {};
class YY {};
class ZZ {};
class VV {};

// it is possible to support differently cv-qualified versions
YY operator*(XX&, YY&) { return YY(); }
ZZ operator*(const XX&, const YY&) { return ZZ(); }
XX operator*(volatile XX&, volatile YY&) { return XX(); }
VV operator*(const volatile XX&, const volatile YY&) { return VV(); }

// the traits can be more complex:
template <class T>
class my_vector {};

template<class A, class B> 
my_vector<typename return_type_2<arithmetic_action<plus_action>, A&, B&>::type>
operator+(const my_vector<A>& a, const my_vector<B>& b)
{
  typedef typename 
    return_type_2<arithmetic_action<plus_action>, A&, B&>::type res_type;
  return my_vector<res_type>();
}



// bitwise ops:
X operator<<(const X&, const Y&) { return X(); }
Z operator>>(const X&, const Y&) { return Z(); }
Z operator&(const X&, const Y&) { return Z(); }
Z operator|(const X&, const Y&) { return Z(); }
Z operator^(const X&, const Y&) { return Z(); }

// comparison ops:

X operator<(const X&, const Y&) { return X(); }
Z operator>(const X&, const Y&) { return Z(); }
Z operator<=(const X&, const Y&) { return Z(); }
Z operator>=(const X&, const Y&) { return Z(); }
Z operator==(const X&, const Y&) { return Z(); }
Z operator!=(const X&, const Y&) { return Z(); }

// logical 

X operator&&(const X&, const Y&) { return X(); }
Z operator||(const X&, const Y&) { return Z(); }

// arithh assignment

Z operator+=( X&, const Y&) { return Z(); }
Z operator-=( X&, const Y&) { return Z(); }
Y operator*=( X&, const Y&) { return Y(); }
Z operator/=( X&, const Y&) { return Z(); }
Z operator%=( X&, const Y&) { return Z(); }

// bitwise assignment
Z operator<<=( X&, const Y&) { return Z(); }
Z operator>>=( X&, const Y&) { return Z(); }
Y operator&=( X&, const Y&) { return Y(); }
Z operator|=( X&, const Y&) { return Z(); }
Z operator^=( X&, const Y&) { return Z(); }

// assignment
class Assign {
public:
  void operator=(const Assign& a) {}
  X operator[](const int& i) { return X(); }
};



namespace boost {
namespace lambda {
  
  // you can do action groups
template<class Act> 
struct plain_return_type_2<arithmetic_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<arithmetic_action<multiply_action>, X, Y> {
  typedef X type;
};

  // if you want to make a distinction between differently cv-qualified
  // types, you need to specialize on a different level:
template<> 
struct return_type_2<arithmetic_action<multiply_action>, XX, YY> {
  typedef YY type;
};
template<> 
struct return_type_2<arithmetic_action<multiply_action>, const XX, const YY> {
  typedef ZZ type;
};
template<> 
struct return_type_2<arithmetic_action<multiply_action>, volatile XX, volatile YY> {
  typedef XX type;
};
template<> 
struct return_type_2<arithmetic_action<multiply_action>, volatile const XX, const volatile YY> {
  typedef VV type;
};

  // the mapping can be more complex:
template<class A, class B> 
struct plain_return_type_2<arithmetic_action<plus_action>, my_vector<A>, my_vector<B> > {
  typedef typename 
    return_type_2<arithmetic_action<plus_action>, A&, B&>::type res_type;
  typedef my_vector<res_type> type;
};

  // bitwise binary:
  // you can do action groups
template<class Act> 
struct plain_return_type_2<bitwise_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<bitwise_action<leftshift_action>, X, Y> {
  typedef X type;
};

  // comparison binary:
  // you can do action groups
template<class Act> 
struct plain_return_type_2<relational_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<relational_action<less_action>, X, Y> {
  typedef X type;
};

  // logical binary:
  // you can do action groups
template<class Act> 
struct plain_return_type_2<logical_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<logical_action<and_action>, X, Y> {
  typedef X type;
};

  // arithmetic assignment :
  // you can do action groups
template<class Act> 
struct plain_return_type_2<arithmetic_assignment_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<arithmetic_assignment_action<multiply_action>, X, Y> {
  typedef Y type;
};

  // arithmetic assignment :
  // you can do action groups
template<class Act> 
struct plain_return_type_2<bitwise_assignment_action<Act>, X, Y> {
  typedef Z type;
};

  // or specialize the exact action
template<> 
struct plain_return_type_2<bitwise_assignment_action<and_action>, X, Y> {
  typedef Y type;
};

  // assignment
template<> 
struct plain_return_type_2<other_action<assignment_action>, Assign, Assign> {
  typedef void type;
};
  // subscript
template<> 
struct plain_return_type_2<other_action<subscript_action>, Assign, int> {
  typedef X type;
};


} // end lambda
} // end boost



void test_binary_operators() {

  X x; Y y;
  (_1 + _2)(x, y);
  (_1 - _2)(x, y);
  (_1 * _2)(x, y);
  (_1 / _2)(x, y);
  (_1 % _2)(x, y);


  // make a distinction between differently cv-qualified operators
  XX xx; YY yy;
  const XX& cxx = xx;
  const YY& cyy = yy;
  volatile XX& vxx = xx;
  volatile YY& vyy = yy;
  const volatile XX& cvxx = xx;
  const volatile YY& cvyy = yy;

  ZZ dummy1 = (_1 * _2)(cxx, cyy);
  YY dummy2 = (_1 * _2)(xx, yy);
  XX dummy3 = (_1 * _2)(vxx, vyy);
  VV dummy4 = (_1 * _2)(cvxx, cvyy);

  my_vector<int> v1; my_vector<double> v2;
  my_vector<double> d = (_1 + _2)(v1, v2);

  // bitwise

  (_1 << _2)(x, y);
  (_1 >> _2)(x, y);
  (_1 | _2)(x, y);
  (_1 & _2)(x, y);
  (_1 ^ _2)(x, y);

  // comparison
  
  (_1 < _2)(x, y);
  (_1 > _2)(x, y);
  (_1 <= _2)(x, y);
  (_1 >= _2)(x, y);
  (_1 == _2)(x, y);
  (_1 != _2)(x, y);

  // logical
 
  (_1 || _2)(x, y);
  (_1 && _2)(x, y);

  // arithmetic assignment
  (_1 += _2)(x, y);
  (_1 -= _2)(x, y);
  (_1 *= _2)(x, y);
  (_1 /= _2)(x, y);
  (_1 %= _2)(x, y);
 
  // bitwise assignment
  (_1 <<= _2)(x, y);
  (_1 >>= _2)(x, y);
  (_1 |= _2)(x, y);
  (_1 &= _2)(x, y);
  (_1 ^= _2)(x, y);

}


int test_main(int, char *[]) {
  test_unary_operators();
  test_binary_operators();
  return 0;
}






