Boost logo

Boost :

From: Maurizio Vitale (mav_at_[hidden])
Date: 2007-04-25 09:23:49


I don't know if it is a bug or not, but I was surprised by the effect
of a seemingly minor code change.

I used to have my expressions, extending proto's ones. Till I needed to assign
to ints and unsigned ints, all was good.

template <typename Expr>
struct my_expression : proto::extends<Expr, my_expression<Expr>, my_domain> {
  typedef proto::extends<Expr, my_expression<Expr>, my_domain> base_type;

  my_expression (Expr const& expr = Expr()) : base_type (expr) {};

  using base_type::operator =;

  operator int () const {
    return static_cast<int>(proto::eval(*this, my_context<Expr> ()));
  }

  operator unsigned int () const {
    return static_cast<unsigned int>(proto::eval(*this, my_context<Expr> ()));
  }
};

This was the case for the example I posted to this mailing list.

In the final code, I start having a need to assign to more types (so that my integers
can have smaller sizes when people optimize for size or the architecture allows
for fast accesses to smaller objects).

My first example involved assigning to a signed char, and if all you have are
operator int and operator unsigned int, like above, this is ambiguous.

Fine, we can add the appropriate operator signed char ():

  operator signed char () const {
    return static_cast<signed char>(proto::eval(*this, my_context<Expr> ()));
  }

This works just fine, but the code is just uselessly repeated (and later will be
a bit more complicated than this, so I'd like to avoid duplication).

So I said to myself: let's get rid of all those operators and replace them with
a single templatized version, like:

template <typename Expr>
struct my_expression : proto::extends<Expr, my_expression<Expr>, my_domain> {
  typedef proto::extends<Expr, my_expression<Expr>, my_domain> base_type;

  my_expression (Expr const& expr = Expr()) : base_type (expr) {};

  using base_type::operator =;

  template<typename T>
  operator T () const {
    return static_cast<T>(proto::eval(*this, my_context<Expr> ()));
  }
};

When I tried it, gcc was not pleased with my idea, the code, or both, and I got errors like (when compiling a main with something like my_int<4> x; char c = x*x;):

./boost/xpressive/proto/operators.hpp: In static member function ¡static typename boost::proto::generate<typename Right::domain, boost::proto::expr<Tag, boost::proto::args2<typename boost::proto::generate<typename Right::domain, boost::proto::expr<boost::proto::tag::terminal, boost::proto::args1<Left&>, boost::proto::args1<Left&>::size> >::type, boost::proto::ref<Right> >, boost::proto::args2<typename boost::proto::generate<typename Right::domain, boost::proto::expr<boost::proto::tag::terminal, boost::proto::args1<Left&>, boost::proto::args1<Left&>::size> >::type,boost::proto::ref<Right> >::size> >::type boost::proto::detail::as_expr_if2<Tag, Left, Right, void, typename Right::is_boost_proto_expr_>::make(Left&, Right&) [with Tag = boost::proto::tag::multiply, Left = const int, Right = sc_int<10>]¢:
./boost/xpressive/proto/operators.hpp:163: instantiated from ¡const typename boost::proto::detail::as_expr_if<boost::proto::tag::multiply, const Left, Right, void, void>::type boost::proto::operator*(const Left&, Right&) [with Left = int, Right = sc_int<10>]¢
pdl.cpp:708: instantiated from here
./boost/xpressive/proto/operators.hpp:77: error: too many initializers for ¡boost::proto::expr<boost::proto::tag::multiply, boost::proto::args2<systemc_expression<boost::proto::expr<boost::proto::tag::terminal, boost::proto::args1<const int&>, 1l> >, boost::proto::ref<sc_int<10> > >, 2l>¢

I include the full source that exibits this, in case my explanation above lacked some critical detail:

#include <iostream>

#include <boost/xpressive/proto/proto.hpp>
#include <boost/xpressive/proto/context.hpp>
#include <boost/xpressive/proto/extends.hpp>
#include <boost/xpressive/proto/debug.hpp>
#include <boost/xpressive/proto/transform/arg.hpp>
#include <boost/xpressive/proto/transform/construct.hpp>
//#include <boost/xpressive/proto/transform/fold_to_list.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/typeof/std/ostream.hpp>

#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/vector.hpp>

#include <boost/test/unit_test.hpp>

namespace proto=boost::proto;
namespace mpl=boost::mpl;

enum binary_representation_enum {magnitude, two_complement};
typedef mpl::integral_c<binary_representation_enum, magnitude> magnitude_c;
typedef mpl::integral_c<binary_representation_enum, two_complement> two_complement_c;

//............... CONFIGURATION ..............................................................................
//typedef magnitude_c priority; // default C++ mode, expressions containing unsigned are unsigned
typedef two_complement_c priority; // expression contained signed are signed

struct my_domain : proto::domain<struct my_grammar> {}; // accept mix of signed/unsigned
//struct my_domain : proto::domain<struct my_segregated_grammar> {}; // signed and unsigned cannot mix
//............... Configuration ..............................................................................

template<typename> struct my_context;

template <typename Expr>
struct my_expr : proto::extends<Expr, my_expr<Expr>, my_domain> {
  typedef proto::extends<Expr, my_expr<Expr>, my_domain> base_type;

  my_expr (Expr const& expr = Expr()) : base_type (expr) {};

  using base_type::operator =;

  template<typename T>
  operator T () const {
    return static_cast<T>(proto::eval(*this, my_context<Expr> ()));
  }
  
// operator int () const {
// return static_cast<int>(proto::eval(*this, my_context<Expr> ()));
// }

// operator unsigned int () const {
// return static_cast<unsigned int>(proto::eval(*this, my_context<Expr> ()));
// }
};

template<typename Grammar, typename Priority>
struct binary_return_type : Grammar {

  template<typename T> struct other : mpl::if_<boost::is_same<T, magnitude_c>, two_complement_c, magnitude_c> {};

  template<typename Expr, typename State, typename Visitor>
  struct apply {
    typedef typename mpl::apply_wrap3<Grammar, Expr, State, Visitor>::type expr_type;
    typedef typename proto::result_of::left<expr_type>::type left_type;
    typedef typename proto::result_of::right<expr_type>::type right_type;
    typedef typename mpl::if_<mpl::contains<mpl::vector<left_type, right_type>, Priority>, Priority, typename other<Priority>::type>::type type;
  };
  
  template<typename Expr, typename State, typename Visitor>
  static typename apply<Expr, State, Visitor>::type
  call (const Expr&, const State&, Visitor&) {
    return typename apply<Expr, State, Visitor>::type ();
  }
};

template<typename, typename> struct number;

template<typename T, typename Representation>
std::ostream& operator << (std::ostream& os, const number<T, Representation> o);

template<typename T, typename Representation>
struct number {
  friend std::ostream& operator << <> (std::ostream& os, const number<T,Representation> o);
  unsigned int m_data;
};

template<typename T>
struct number<T, two_complement_c> {
  friend std::ostream& operator << <> (std::ostream& os, const number<T,two_complement_c> o);
  int m_data;
};

template<typename T>
std::ostream& operator << (std::ostream& os, const number<T, magnitude_c> n) { os << "~U~" << n.m_data << "~"; return os; }

template<typename T>
std::ostream& operator << (std::ostream& os, const number<T, two_complement_c> n) { os << "~S~" << n.m_data << "~"; return os; }

template<typename Priority>
struct return_type_grammar : proto::or_ <
  proto::trans::always<proto::terminal<number<proto::_,magnitude_c> >, magnitude_c>,
  proto::trans::always<proto::terminal<number<proto::_,two_complement_c> >, two_complement_c>,
  proto::trans::always<proto::terminal<int>, two_complement_c>,
  proto::trans::always<proto::terminal<unsigned int>, magnitude_c>,

  proto::trans::arg<proto::unary_expr<proto::_, return_type_grammar<Priority> > >,
  binary_return_type<proto::binary_expr<proto::_, return_type_grammar<Priority>, return_type_grammar<Priority> >, Priority >
>
{};

struct my_grammar : proto::or_ <
  proto::terminal<number<proto::_,two_complement_c> > ,
  proto::terminal<number<proto::_,magnitude_c> >,
  proto::terminal<int>,
  proto::terminal<unsigned int>,
  proto::unary_expr<proto::_, my_grammar> ,
  proto::binary_expr<proto::_, my_grammar, my_grammar>
>
{};

struct my_unsigned_grammar : proto::or_ <
  proto::terminal<number<proto::_,magnitude_c> >,
  proto::terminal<unsigned int>,
  proto::unary_expr<proto::_, my_unsigned_grammar> ,
  proto::binary_expr<proto::_, my_unsigned_grammar, my_unsigned_grammar>
>
{};

struct my_signed_grammar : proto::or_ <
  proto::terminal<number<proto::_,two_complement_c> > ,
  proto::terminal<int>,
  proto::unary_expr<proto::_, my_signed_grammar> ,
  proto::binary_expr<proto::_, my_signed_grammar, my_signed_grammar>
>
{};

struct my_segregated_grammar : proto::or_ <
  my_signed_grammar,
  my_unsigned_grammar
> {};

template<typename Expr, typename Priority>
struct return_type : return_type_grammar<Priority>::template apply<Expr, magnitude_c, mpl::void_>
{};

namespace boost { namespace proto {
  template<typename Expr>
  struct generate<my_domain, Expr> {
    typedef my_expr<Expr> type;
      
    static type make (Expr const& expr) {
      return type (expr);
    }
  };
} } // end namespace boost::proto

template<typename Expr>
struct my_context : proto::callable_context<const my_context<Expr> > {

  typedef typename mpl::if_<boost::is_same<typename return_type<Expr, ::priority>::type, magnitude_c>,
                            unsigned int,
                            int>::type result_type;

  template<typename T>
  unsigned int operator () (proto::tag::terminal, number<T, magnitude_c> n) const { return n.m_data; }

  template<typename T>
  int operator () (proto::tag::terminal, number<T, two_complement_c> n) const { return n.m_data; }
};

template<typename Expr>
std::ostream& operator << (std::ostream& os, const my_expr<Expr> e)
{ os << "my_expr<" << proto::eval (e, my_context<Expr>()) << ">"; return os; }

template<int N>
struct my_int : my_expr<typename proto::terminal<number<mpl::int_<N>, two_complement_c > >::type>
{
  typedef number<mpl::int_<N>, two_complement_c > number_type;
  typedef my_expr<typename proto::terminal<number_type>::type> expr_type;

  my_int () {}
  my_int (int i) : expr_type (expr_type::type::make (i)) {}

  template<typename Expr>
  my_int& operator = (const my_expr<Expr>& e) {
    proto::arg (*this).m_data = static_cast<int>(proto::eval(e, my_context<Expr> ()));
    return *this;
  }

  template<typename T>
  my_int& operator = (T value) {
    proto::arg (*this).m_data = value;
    return *this;
  }

  friend std::ostream& operator << (std::ostream& os, const my_int n) { os << "my_int<" << proto::arg(n).m_data << ">"; return os; }
};

template<int N>
struct my_uint : my_expr<typename proto::terminal<number<mpl::int_<N>, magnitude_c > >::type>
{
  typedef my_expr<typename proto::terminal<number<mpl::int_<N>, magnitude_c > >::type> expr_type;

  my_uint () {}
  my_uint (int i) : expr_type (expr_type::type::make (i)) {}

  template<typename Expr>
  my_uint& operator = (const Expr& e) {
    proto::arg (*this).m_data = proto::eval(e, my_context<Expr> ());
    return *this;
  }
};

#include <boost/test/included/unit_test_framework.hpp>
using namespace boost::unit_test;

template<typename Expr>
void test (const Expr& expr) {
  std::cout << "Expression:\n";
    display_expr (expr);
  if (proto::matches<Expr, my_grammar>::value)
    std::cout << "matches my grammar\n\n";
  else
    std::cout << "doesn't matches my grammar\n\n";
}

void misc_test () {
  my_int<6> i4(-22);
  my_uint<6> ui4(7);

  int i;
  unsigned int j;

  i4 = 5;
  i4 = 5*ui4;
  i4 = 5*ui4+i4;

  i = i4/ui4+4;
  j = i4/ui4;

  char jj = i4/ui4;

  // display_expr (5);
  display_expr (i4/ui4+4);

  std::cout << "my_expr, i4/ui4 =" << i4/ui4 << "\n";
  std::cout << "my_expr, i4 =" << i4 << "\n"; // ambiguous
  std::cout << "my_expr, -22/7 [assigned to int]=" << i << "\n";
  std::cout << "my_expr, -22/7 [assigned to unsigned int]=" << j << "\n";
}

test_suite*
init_unit_test_suite (int, char**)
{
  test_suite* test= BOOST_TEST_SUITE( "boost::proto sandbox" );

  test->add (BOOST_TEST_CASE (&misc_test));

  return test;
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk