If you want to produce syntaxes of valid C++ expressions (possibly as a subset of all C++ expressions, much like do-notation limits itself to monad operations), validate them statically and consume them, then your best bet is Boost.Proto. To describe it succinctly, it is itself an EDSL to write and describe EDSLs.
I won't go in any more detail on how to use it. Although it may be hard to learn to use, especially so if you're not accustomed to C++ metaprogramming, the documentation is great and if you've ever written a grammar I believe you will find your marks. In another of my answers I walked someone on how to write an EDSL with a grammar that only accepts simple arithmetic expressions and that consumes them to compute their derivative, so you may want to check that.
As for your exact question, I'm afraid the answer has to be either a short "no, you can't do that", or a long "you can do it to an extent as Boost.Phoenix shows, but it's probably not worth your time implementing it considering the cryptic errors and/or extra compilation time for your EDSL users". My reasoning for this is what you want to do fits on two levels: do-notation is a Haskell specific feature, while consuming a syntax tree and giving semantics to it in on the level of the EDSL itself.
As it so happens, typical Proto-style EDSL are valid C++ expressions, and the language doesn't offer scoping at that level, variables are declared in separate statements. For instance, _a + _b
is a valid C++ fragment because _a
and _b
are declared C++ variables provided by Phoenix, but not a valid program in the EDSL because _a
and _b
are left unbound. Yes, the error will be caught, but you have to implement that yourself. In comparison do-notation is part of Haskell so any EDSL inherits it for free. That is to say, return (a + b)
is never valid on its own -- there needs to be some a
and some b
.
There are some things to keep in mind though. C++11 offers lambda expressions, so you can in fact get some scoping here -- but those will be opaque in the EDSL, the syntax tree will only show a variable. Some introspection might reveal that that variable is callable for some types but that's it. Even if you require that the lambdas return a value in the EDSL there's no telling what else they could do. That's not always worth worrying about and I'd say that it's perfectly appropriate for some EDSLs.
Similarly C++11 makes it much more easy to 'factor out' parts of an EDSL expression. That's not so much equivalent to the a <- foo
sugar of do-notation but it is equivalent to let a = foo
. So you could in fact without much difficulty makes the following do 'the right thing':
auto double_pop = make_tuple(pop(), pop());
auto program = (push(3), push(4), consume(double_pop));
Which could be equivalent to the unidiomatic and contrived following:
program = do
let a = pop
return consume `ap` a `ap` a
(Since Boost.Proto started as a C++03 library make sure to pore over the docs before using C++11's auto
with it, IIRC there is a caveat.)