2

This is a follow-up on this Q&A. I now have several data structures in a namespace ast, subdivided over two sub-namespaces (algebraic and numeric) that correspond to the two different formats that the grammar recognizes.

namespace ast {    
    namespace algebraic {            
        struct occupance
        { 
            char pc; 
            char col;
            int row; 
        };

        using pieces = std::vector<occupance>;

        struct placement 
        { 
            char c; 
            boost::optional<pieces> p; 
        };        
    }

    namespace numeric {            
        struct occupance
        { 
            char pc; 
            int sq; 
        };

        struct range 
        { 
            occupance oc; 
            int sq; 
        };

        using pieces = std::vector<boost::variant<range, occupance>>;

        struct placement 
        { 
            char c; 
            boost::optional<pieces> p; 
        };            
    }

    struct fen 
    { 
        char c; 
        std::vector<boost::variant<numeric::placement, algebraic::placement>> p; 
    };
}        

Working parser Live On Coliru

The trouble starts when I try to define streaming operators for the various types. With the generic operator<< taking a vector<T> in the same namespace as the various ast structs (as in the linked Q&A), all is fine. But once I have two sub-namespaces algebraic and numeric and define the various operators in these namespaces:

namespace ast {
    template <typename T> 
    std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) 
    {
        os << "{"; 
        for (auto const& el : v) 
            os << el << " "; 
        return os << "}";
    }        

    namespace algebraic {            
        std::ostream& operator<<(std::ostream& os, occupance const& oc)  
        { 
            return os << oc.pc << oc.col << oc.row; 
        }

        std::ostream& operator<<(std::ostream& os, placement const& p)     
        { 
            return os << p.c << " " << p.p; 
        }         
    }   // algebriac

    namespace numeric {
        std::ostream& operator<<(std::ostream& os, occupance const& oc)  
        { 
            return os << oc.pc << oc.sq; 
        }

        std::ostream& operator<<(std::ostream& os, range const& r) 
        { 
            for (auto sq = r.oc.sq; sq <= r.sq; ++sq)
                os << r.oc.pc << sq << " ";
            return os;
        }

        std::ostream& operator<<(std::ostream& os, placement const& p)     
        { 
            return os << p.c << " " << p.p; 
        }         
    }   // numeric
}   // ast

Live On Coliru the appropriate operators are no longer being found.

In file included from main.cpp:4:
/usr/local/include/boost/optional/optional_io.hpp:47:21: error: invalid operands to binary expression ('basic_ostream<char, std::__1::char_traits<char> >' and 'const std::__1::vector<ast::algebraic::occupance, std::__1::allocator<ast::algebraic::occupance> >')
    else out << ' ' << *v ;
         ~~~~~~~~~~ ^  ~~
main.cpp:79:37: note: in instantiation of function template specialization 'boost::operator<<<char, std::__1::char_traits<char>, std::__1::vector<ast::algebraic::occupance, std::__1::allocator<ast::algebraic::occupance> > >' requested here
            return os << p.c << " " << p.p; 

Question: how to define the various streaming operators to properly print the matched AST?

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304

2 Answers2

3

It's run-of-the-mill ADL. (What is "Argument-Dependent Lookup" (aka ADL, or "Koenig Lookup")?)

Either duplicate the operator<< for each subnamespace or use an ADL hook "tag".

Duplicating

Live On Coliru

ADL Hook

Note the adl_hook type and usage:

Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/optional.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <vector>

namespace x3 = boost::spirit::x3;

namespace ast {
    struct adl_hook;

    template <typename T>
        std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
            os << "{"; for (auto const& el : v) os << el << " "; return os << "}";
        }

    namespace algebraic {

        template <typename tag = adl_hook>
            struct occupance_t
        { 
            char pc; 
            char col;
            int row; 
        };

        using occupance = occupance_t<>;

        using pieces = std::vector<occupance>;

        template <typename tag = adl_hook>
        struct placement_t
        { 
            char c; 
            boost::optional<pieces> p; 
        };        

        using placement = placement_t<>;
    }

    namespace numeric {

        template <typename tag = adl_hook>
        struct occupance_t
        { 
            char pc; 
            int sq; 
        };

        using occupance = occupance_t<>;

        struct range 
        { 
            occupance oc; 
            int sq; 
        };

        using piece  = boost::variant<range, occupance>;
        using pieces = std::vector<piece>;

        template <typename tag = adl_hook>
        struct placement_t
        { 
            char c; 
            boost::optional<pieces> p; 
        };            

        using placement = placement_t<>;
    }

    struct fen 
    { 
        char c; 
        using placement = boost::variant<numeric::placement, algebraic::placement>;
        std::vector<placement> p; 
    };

    namespace algebraic {            
        std::ostream& operator<<(std::ostream& os, occupance const& oc)  
        { 
            return os << oc.pc << oc.col << oc.row; 
        }

        std::ostream& operator<<(std::ostream& os, placement const& p)     
        { 
            return os << p.c << " " << p.p; 
        }         
    }   // algebriac

    namespace numeric {
        std::ostream& operator<<(std::ostream& os, occupance const& oc)  
        { 
            return os << oc.pc << oc.sq; 
        }

        std::ostream& operator<<(std::ostream& os, range const& r) 
        { 
            for (auto sq = r.oc.sq; sq <= r.sq; ++sq)
                os << r.oc.pc << sq << " ";
            return os;
        }

        std::ostream& operator<<(std::ostream& os, placement const& p)     
        { 
            return os << p.c << " " << p.p; 
        }         
    }   // numeric
}   // ast

BOOST_FUSION_ADAPT_STRUCT(ast::algebraic::occupance, pc, col, row)
BOOST_FUSION_ADAPT_STRUCT(ast::algebraic::placement, c,  p )

BOOST_FUSION_ADAPT_STRUCT(ast::numeric::occupance, pc, sq)
BOOST_FUSION_ADAPT_STRUCT(ast::numeric::range,     oc, sq)
BOOST_FUSION_ADAPT_STRUCT(ast::numeric::placement, c,  p )

BOOST_FUSION_ADAPT_STRUCT(ast::fen, c,  p )

namespace grammar {        
    auto const colon = x3::lit(':');
    auto const comma = x3::lit(',');
    auto const dash  = x3::lit('-');
    auto const dot   = x3::lit('.');

    template<typename T>
    auto as_rule = [](auto p) { return x3::rule<struct _, T>{} = x3::as_parser(p); };

    auto const piece_type = x3::char_('K') | x3::attr('M');
    auto const color      = x3::char_("BW");

    namespace algebraic {        
        auto const square     = x3::lower >> x3::uint_;
        auto const occupance  = as_rule<ast::algebraic::occupance> ( piece_type >> square      ); 
        auto const pieces     = as_rule<ast::algebraic::pieces>    ( occupance % comma         ); 
        auto const placement  = as_rule<ast::algebraic::placement> ( colon >> color >> -pieces );
    }   // algebraic

    namespace numeric {        
        auto const square     = x3::uint_;
        auto const occupance  = as_rule<ast::numeric::occupance> ( piece_type >> square        ); 
        auto const range      = as_rule<ast::numeric::range>     ( occupance >> dash >> square ); 
        auto const pieces     = as_rule<ast::numeric::pieces>    ( (range | occupance) % comma ); 
        auto const placement  = as_rule<ast::numeric::placement> ( colon >> color >> -pieces   );
    }   // numeric

    auto const fen = as_rule<ast::fen> ( color >> (x3::repeat(2)[numeric::placement] | x3::repeat(2)[algebraic::placement]) >> -dot ); 
}   // grammar

int main() {
    for (std::string const t : {
        "W:Wa1,c1,e1,g1,b2,d2,f2,h2,a3,c3,e3,g3:Bb8,d8,f8,h8,a7,c7,e7,g7,b6,d6,f6,h6",
        "W:BKa1,Ka3:WKb8,Kd8",
        "B:W18,24,27,28,K10,K15:B12,16,20,K22,K25,K29",
        "B:W18,19,21,23,24,26,29,30,31,32:B1,2,3,4,6,7,9,10,11,12",
        "W:B1-20:W31-50",   // initial position
        "W:B:W",            // empty board
        "W:B1:W",           // only black pieces
        "W:B:W50"           // only white pieces
    }) {
        auto b = t.begin(), e = t.end();
        ast::fen data;
        bool ok = phrase_parse(b, e, grammar::fen, x3::space, data);

        std::cout << t << "\n";
        if (ok) {
            std::cout << "\t Parsed: \n" << boost::fusion::as_vector(data) << "\n";
        } else {
            std::cout << "Parse failed:\n";
            std::cout << "\t on input: " << t << "\n";
        }
        if (b != e)
            std::cout << "\t Remaining unparsed: '" << std::string(b, e) << '\n';
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Completed both sample approaches – sehe Jan 02 '16 at 13:29
  • try uncommenting also the `<< boost::fusion::as_vector(data)`, then I still get an error (sorry, should have said that in my Q, it was outcommented to silence the compiler) – TemplateRex Jan 02 '16 at 13:29
  • 1
    [**Fixed²**](http://coliru.stacked-crooked.com/a/ab5e86d9638cb753) (that was a different error, still ADL hooking involved. This time I solved it by making the variants declared inside the actual namespace) – sehe Jan 02 '16 at 13:34
  • @TemplateRex [at least one of the `placement`s need also a tag](http://coliru.stacked-crooked.com/a/e5b674c2fabcbd03). – llonesmiz Jan 02 '16 at 13:35
  • Or you can use the approach I did, which seems to be the x3 preferred approach anyways :) – sehe Jan 02 '16 at 13:35
  • @cv_and_he yes, your print does print the full parse – TemplateRex Jan 02 '16 at 13:39
  • @sehe duplicating is not viable, if both namespace get pulled in, you get an overload resolution ambiguity – TemplateRex Jan 02 '16 at 13:43
  • @TemplateRex Something like [this](http://coliru.stacked-crooked.com/a/b141bfd6d163ffde) could work, although it is not pretty. – llonesmiz Jan 02 '16 at 13:44
  • @TemplateRex not true. `use`d names do not participate in ADL. ADL is strictly about the declaring namespace (not saying duplicating is nice, but for that matter I don't think `operator<<` is nice _at all_) – sehe Jan 02 '16 at 13:51
  • Re Fixed² I suppose it's because `x3::variant` lacks io? I'm not sure - no time to investigate – sehe Jan 02 '16 at 13:52
  • @sehe so if e.g. a `variant, vector>` gets passed to `operator<<(os, variant)`, don't both the `operator(os, vector)` in N1 and N2 get pulled in by ADL? – TemplateRex Jan 02 '16 at 13:52
  • Oh yeah, I forgot that. Indeed, that would lead to conflict – sehe Jan 02 '16 at 14:17
  • @sehe althoug in hindsight, this problem was indeed a familiar theme, but it's not quite *run-of-the-mill* ADL. Usually, ADL is *too greedy*, but here, ordinary lookup on e.g. `boost::variant` finds an `operator<<` in namespaces `numeric` and `algebraic` and does not proceed the enclosing namespace `ast`. And `ast` is also not an associated namespace (only the immediately enclosing scopes are). Most of the time (99/100?), one is concerned with disabling/limiting ADL, but here it's about encouraging ADL! – TemplateRex Jan 02 '16 at 19:22
  • Sounds like run of the mill to me. Associated namespaces are associated namespaces :) I knew it when I wrote it. (It's sometimes almost impossible to get `std::vector` to stream for your types correctly.) – sehe Jan 02 '16 at 19:24
  • @sehe I also discovered your stream-tagging technique, that also looks nice and non-intrusive on the types to be streamed – TemplateRex Jan 02 '16 at 19:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99553/discussion-between-sehe-and-templaterex). – sehe Jan 02 '16 at 20:58
  • Fixed the missing adl_hook on `placement`. [see also](http://coliru.stacked-crooked.com/a/6f1747493a288a49) – sehe Jan 02 '16 at 23:31
2

In addition to the fine answer by @sehe, I decided to side-step all the ADL issues and follow the Spirit X3 lecture notes and write a dedicated printer class, that loops over vector elements by hand.

struct printer
{
    std::ostream& out;

    printer(std::ostream& os) : out(os) {}

    auto operator()(algebraic::occupance const& oc) const   
    { 
        out << oc.piece_ << oc.column_ << oc.row_ << " "; 
    }

    auto operator()(std::vector<algebraic::color_placement> const& cps) const    
    { 
        for (auto const& cp : cps) {
            out << " { " << cp.color_ << " : "; 
            for (auto const& elem : cp.placement_)
                (*this)(elem);
            out << " } ";
        }
    }

    auto operator()(numeric::occupance const& oc) const
    { 
        out << oc.piece_ << oc.square_ << " ";
    }

    auto operator()(numeric::range const& r) const
    { 
        auto const occupance = r.occupance_;
        for (auto square = occupance.square_; square <= r.square_; ++square)
            out << occupance.piece_ << square << " ";            
    }

    auto operator()(std::vector<numeric::color_placement> const& cps) const    
    { 
        for (auto const& cp : cps) {
            out << " { " << cp.color_ << " : "; 
            for (auto const& elem : cp.placement_)
                boost::apply_visitor(*this, elem);
            out << " } ";
        }
    }

    auto operator()(fen const& f) const
    {
        out << f.color_; 
        boost::apply_visitor(*this, f.color_placements_);   
    }
};    

Live On Coliru

In addition, I cleaned up the grammar a bit by reducing the boost::optional<std::vector<T>> to the compatible attribute std::vector<T>, so that no more dependencies on boost::optional remain.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304