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

Maurizio, I didn't see this message the first time because for some reason, all your messages appear out of order on GMane. Is the clock on your computer wrong? Is it something you can fix? Maurizio Vitale wrote:
I don't know if it is a bug or not, but I was surprised by the effect of a seemingly minor code change.
<snip>
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> ())); } };
OK, your expression wrapper has an implicit conversion to any type T. That is, IMO, a bad idea, but nevertheless, proto shouldn't stop you from doing that. I think I have fixed the problem in CVS. Incidentally, you were running into an ambiguous conversion deep in the guts of callable_context, which uses an implicit conversion to a hidden type to detect whether your context has an overload of operator() that accepts the current expression. The code now uses a less-preferred conversion, avoiding the ambiguity with yours. -- Eric Niebler Boost Consulting www.boost-consulting.com

Eric Niebler <eric@boost-consulting.com> writes:
Maurizio, I didn't see this message the first time because for some reason, all your messages appear out of order on GMane. Is the clock on your computer wrong? Is it something you can fix?
Should be fixed now. I noticed the problem yesterday when I started backing up to Amazon S3.
Maurizio Vitale wrote:
I don't know if it is a bug or not, but I was surprised by the effect of a seemingly minor code change.
<snip>
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> ())); } };
OK, your expression wrapper has an implicit conversion to any type T. That is, IMO, a bad idea, but nevertheless, proto shouldn't stop you from doing that.
The full plan is to later limit to a set of user specified (builtin) types. I cannot boost::enable_if the operator itself, but since I'll need a number of transformations as part of eval (among those quantization and overflow handling), I'll fail with as a meaningful error message as possible somewhere in the pipe. At least this is what I hope. So builtin_type v = expression should pass through the operator T() above, but my_int v = expression should not. I hope this will work without ambiguities, otherwise I'll clearly remove operator T () here. Does the plan still sound bad?
I think I have fixed the problem in CVS.
Thanks a lot.
Incidentally, you were running into an ambiguous conversion deep in the guts of callable_context, which uses an implicit conversion to a hidden type to detect whether your context has an overload of operator() that accepts the current expression. The code now uses a less-preferred conversion, avoiding the ambiguity with yours.
I take this is the explanation why operator T() above had problems. Best regards, Maurizio
participants (2)
-
Eric Niebler
-
Maurizio Vitale