Subscribe to 10 GOTO 10        RSS Feed
-----

Fun with Boost::proto

Icon Leave Comment
So I am working on a theoretical blog post on expressions based upon some of the little nuances that I have learned over the last week. Some neat stuff. In my research I came upon a neat little series of articles called "Expressive C++" by Eric Niebler and in it there was this neat little example (with the added puzzle of anything between angle brackets missing):

//Code by Eric Niebler: http://cpp-next.com/archive/2010/08/expressive-c-introduction/
#include <string>
#include <boost/proto/proto.hpp>
namespace proto = boost::proto;
 
int main()
{
    // Create a Proto terminal that wraps a string.
    // Let's be cheeky and call it "cout".
    proto::terminal< std::string >::type cout = { "cout" };
 
    // Create an expression tree and pass it to display_expr
    // for pretty-printing.
    proto::display_expr(
        cout << "hello" << ' ' << "proto!"
    );
}


This little gem will output a "tree for us whose structure corresponds to the precedence and associativity of the C++ operators." -- neat.

So I thought I would modify the code every so slightly and see what happened:
//Code by Eric Niebler: http://cpp-next.com/archive/2010/08/expressive-c-introduction/
#include <string>
#include <boost/proto/proto.hpp>
namespace proto = boost::proto;
 
int main()
{
    // Create a Proto terminal that wraps a string.
    // Let's be cheeky and call it "cout".
    proto::terminal< std::string >::type cout = { "cout" };
 
    // Create an expression tree and pass it to display_expr
    // for pretty-printing.
    proto::display_expr(
        cout << "hello" << ' ' << "proto!" << 2 + 3
    );
}


Now when I play around like this I generally have an idea of what I expect to see. That is kind of the idea to use a pseudo-scientific meathod. I have a hypothosis of what the output will be, and I run the program and test my hypothosis... this is what I expected:
shift_left(
    shift_left(
        shift_left(
            shift_left(
                terminal(cout)
              , terminal(hello)
            )
          , terminal( )
        )
      , terminal(proto!)
    )
  , plus(
        terminal(2)
      , terminal(3)
    )
)


this is what I got back:
> "C:\CProjects\Forum Help\helloproto.exe " 
shift_left(
    shift_left(
        shift_left(
            shift_left(
                terminal(cout)
              , terminal(hello)
            )
          , terminal( )
        )
      , terminal(proto!)
    )
  , terminal(5)
)


Neat-o!!! -- the constant expression has been evaluated and replaced with the evaluated value! This was precisely one of the things I was going to talk about in my blog entry: expressions live in two worlds - compile-time and run-time and are "evaluated" in both worlds.

Unfortunately things were not quite as "neat-o" as I thought.

As it turns out the following produces the same kind of result:
//Code by Eric Niebler: http://cpp-next.com/archive/2010/08/expressive-c-introduction/
#include <string>
#include <cstdlib>
#include <boost/proto/proto.hpp>
namespace proto = boost::proto;
 
int main()
{
    // Create a Proto terminal that wraps a string.
    // Let's be cheeky and call it "cout".
    proto::terminal< std::string >::type cout = { "cout" };
    int two = rand();

    // Create an expression tree and pass it to display_expr
    // for pretty-printing.
    proto::display_expr(
        cout << "hello" << ' ' << "proto!" << two + 3
    );
}


the output for me was:
> "C:\CProjects\Forum Help\helloproto.exe " 
shift_left(
    shift_left(
        shift_left(
            shift_left(
                terminal(cout)
              , terminal(hello)
            )
          , terminal( )
        )
      , terminal(proto!)
    )
  , terminal(44)
)


It seems unlikely that the compiler knew the value of two and replaced thus evaluated "two+3" at compile time. Alas, proto was actually evaluating the addition at run time.

So I will have to go back to the idea of using assembly listings to see what parts of expression might be evaluated at compile-time. However it turns out that this simple little program is a lot of fun to play with non-the-less.

So... how do we get proto to give the tree that I initially expected? My first attempt was to try "2" + "3" but this spit back a C++ syntax error since you can't add pointers (const char* + const char* is not allowed) -- more evidence that the evaluation of the + operator was happening outside of anything proto was doing. Sooo I tried:

cout << "hello" << ' ' << "proto!" << std::string("2") + "3"

this resulted in terminal(23) -- so my guess of course was that this is because addition is defined for strings! So if I defined operator<<(string, string) as concatenation I should get some output with a terminal "proto!23" -- nope!

#include <cstdlib>
#include <boost/proto/proto.hpp>
namespace proto = boost::proto;

std::string& operator<<(std::string& out, const std::string& lhs) {
    return out += lhs;
}
 
int main()
{
    // Create a Proto terminal that wraps a string.
    // Let's be cheeky and call it "cout".
    proto::terminal< std::string >::type cout = { "cout" };
    int two = rand();

    // Create an expression tree and pass it to display_expr
    // for pretty-printing.
    proto::display_expr(
        cout << "hello" << ' ' << "proto!" << std::string("2") + "3"
    );
}


output still had the terminal(23)!!!

Frustrating. I was beginning to wonder if the compiler was actually evaluating the string's operator+ at compile-time (that would be a neat optimization)

Then looking that the output it hit me: duh! operator precedence and associativity!

Lets look at this example:
//Code by Eric Niebler: http://cpp-next.com/archive/2010/08/expressive-c-introduction/
#include <string>
#include <cstdlib>
#include <boost/proto/proto.hpp>
namespace proto = boost::proto;

int main()
{
    // Create a Proto terminal that wraps a string.
    // Let's be cheeky and call it "cout".
    proto::terminal< std::string >::type cout = { "cout" };
    proto::terminal< int >::type _2 = { 2 };
    proto::terminal< int >::type _3 = { 3 };
    int two = rand();

    // Create an expression tree and pass it to display_expr
    // for pretty-printing.
    proto::display_expr(
        cout << "hello" << ' ' << "proto!" << _2 + _3
    );
}


the output is what I originally expected:
> "C:\CProjects\Forum Help\helloproto.exe " 
shift_left(
    shift_left(
        shift_left(
            shift_left(
                terminal(cout)
              , terminal(hello)
            )
          , terminal( )
        )
      , terminal(proto!)
    )
  , plus(
        terminal(2)
      , terminal(3)
    )
)


now lets remember that the symbol "cout" is of type: proto::terminal< std::string >::type

So cout << "hello" is of type either proto::terminal< std::string >::type OR whatever type is returned by operator<<(proto::terminal< std::string >::type, const char*).

and so cout << "hello" << ' ' is of a some magical proto-based type and so is:
cout << "hello" << ' ' << "proto!"

but the addition... the addition is evaluated before the operator<< immediately to its left. So when I did:

proto-stuff << std::string("2") + "3" the compiler DID execute the string addition first, and then fed the results back to the operator<< for the proto class.

so in the wonderful world of C++ expressions with operator overloading it is important to keep to keep track of the value-type of expressions because those determine which operators get called. So not only does the compiler have the complex task to keeping track of types and conversions etc. but similar models must exist our minds as we write the expressions and parse them with out eyes.

That and programming is a lot of fun. Sure the above looks pretty dry to the outsider. But this was a FUN couple of hours for me!!!

0 Comments On This Entry

 

July 2014

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27 28293031  

Recent Entries

Search My Blog

0 user(s) viewing

0 Guests
0 member(s)
0 anonymous member(s)