Search code examples
c++boostboost-spirit

How to find out what methods in auto-type arguments of lambda?


I have been wrestling with Boost.Spirit for a week. Forgive me for my ignorance about C++'s template metaprogramming, but I can't help to ask: How do you know what can you do with a variable if you don't know its god damn type? For example:

namespace qi = boost::spirit::qi;

qi::on_error<qi::fail>(rule,
  [](auto& args, auto& context, auto& r) {
    // what to do with args, context and r?
  }
);

It's not like Python, where you can use dir() or help() to get information about some variable. I know I can use typeid(..).name() to find out the type of a variable, but usually it turns out to be some long unreadable text like:

struct boost::fusion::vector<class boost::spirit::lex::lexertl::iterator<class boost::spirit::lex::lexertl::functor<struct boost::spirit::lex::lexertl::token<class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::vector<__int64,struct String,struct Symbol,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na>,struct boost::mpl::bool_<1>,unsigned __int64>,class boost::spirit::lex::lexertl::detail::data,class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::bool_<1>,struct boost::mpl::bool_<1> > > & __ptr64,class boost::spirit::lex::lexertl::iterator<class boost::spirit::lex::lexertl::functor<struct boost::spirit::lex::lexertl::token<class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::vector<__int64,struct String,struct Symbol,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na>,struct boost::mpl::bool_<1>,unsigned __int64>,class boost::spirit::lex::lexertl::detail::data,class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::bool_<1>,struct boost::mpl::bool_<1> > > const & __ptr64,class boost::spirit::lex::lexertl::iterator<class boost::spirit::lex::lexertl::functor<struct boost::spirit::lex::lexertl::token<class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::vector<__int64,struct String,struct Symbol,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na,struct boost::mpl::na>,struct boost::mpl::bool_<1>,unsigned __int64>,class boost::spirit::lex::lexertl::detail::data,class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >,struct boost::mpl::bool_<1>,struct boost::mpl::bool_<1> > > const & __ptr64,struct boost::spirit::info const & __ptr64>

So there is only one method left: refer to the documentation. But here's the problem: Spirit's documentation is incomplete. In this example, It only say a few words about these arguments in the "Error Handling Tutorial":

on_error declares our error handler:

on_error<Action>(rule, handler)

...

handler is the actual error handling function. It expects 4 arguments:

Arg Description
first The position of the iterator when the rule with the handler was entered.
last The end of input.
error-pos The actual position of the iterator where the error occurred.
what What failed: a string describing the failure.

But as you can see, on_error's second argument is not a 4-arguments function, but a 3-arguments function (args, context and r). Is the documentation wrong? Where can I find out the methods on these arguments? Does Spirit has a per-funcion API Documentation?


Solution

  • The simplest way:

    qi::on_error<qi::fail>(rule,
      [](auto& args, auto& context, auto& r) {
           std::cerr << __PRETTY_FUNCTION__ << std::endl;
      }
    );
    

    On e.g. GCC this prints the full signature including deduced type arguments.

    Is the documentation wrong?

    Note that it DOES expect 4 arguments:

    using namespace qi::labels;
    qi::on_error<qi::error_handler_result::fail>(rule, f_(_1, _2, _3, _4));
    

    When you install a handler like f_:

    struct F {
        using result_type = void;
    
        template <typename... Args>
        void operator()(Args&&...) const {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
    };
    
    boost::phoenix::function<F> f_{};
    

    It prints (Live):

    "expected" -> true
    void Parser::F::operator()(Args&& ...) const [with Args = {const char*&, const char* const&, const char* const&, const boost::spirit::info&}]
    "unexpected" -> false
    

    As you can see, the documentation is not wrong or incomplete. Your expectations were, because you didn't get that handler represent a deferred function aka. Phoenix Actor. I guess the "devious" thing the documentation did was assume you'd see that from the actual example, which immediately precedes the documentation you quoted:

    on_error<fail>
    (
        xml
      , std::cout
            << val("Error! Expecting ")
            << _4                               // what failed?
            << val(" here: \"")
            << construct<std::string>(_3, _2)   // iterators to error-pos, end
            << val("\"")
            << std::endl
    );
    

    Here, it seems quite obvious that the expression std::cout << val("Error! Expecting ") << _4 << val(" here: \"") << construct<std::string>(_3, _2) << val("\"") << std::endl is not literally "a function taking 4 arguments". It is a Phoenix expression defining an actor that takes 4 arguments, though.

    You did find out that an actor is in fact still implemented in terms of a callable object. And it "takes" 3 arguments, which are the technical implementation details for Phoenix actors and the Spirit context arguments.

    The problem was a disconnect of abstraction levels. The documentation documents the library interface, you accidentally looked at implementation details.

    TL;DR

    A lambda is not a deferred Phoenix actor.

    Learn more about how deferred actors work e.g. with semantic actions:

    Some answers on [SO] that highlight the relation between Actor signature, context and implementation signatures: