Home > Back-end >  How to find out what methods in auto-type arguments of lambda?
How to find out what methods in auto-type arguments of lambda?

Time:05-28

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?

CodePudding user response:

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:

CodePudding user response:

It will generate a compile time error, but if you have a class template declaration like

template <typename T> struct get_type;

Then trying to create a get_type object with a provided type will generate an error and in the error message it will tell you what the type provide to get_type is. For example using

int main()
{
    auto lambda = [](auto first, auto second, auto third) 
    { 
        get_type<decltype(first)>{}; 
        get_type<decltype(second)>{}; 
        get_type<decltype(third)>{};
    };
    lambda(A{}, B{}, int{});
}

produces an error message like

main.cpp:20:9: error: invalid use of incomplete type 'struct get_type<A>'
   20 |         get_type<decltype(first)>{};
      |         ^~~~~~~~
main.cpp:14:30: note: declaration of 'struct get_type<A>'
   14 | template <typename T> struct get_type;
      |                              ^~~~~~~~
main.cpp:21:9: error: invalid use of incomplete type 'struct get_type<B>'
   21 |         get_type<decltype(second)>{};
      |         ^~~~~~~~
main.cpp:14:30: note: declaration of 'struct get_type<B>'
   14 | template <typename T> struct get_type;
      |                              ^~~~~~~~
main.cpp:22:9: error: invalid use of incomplete type 'struct get_type<int>'
   22 |         get_type<decltype(third)>{};
      |         ^~~~~~~~
main.cpp:14:30: note: declaration of 'struct get_type<int>'
   14 | template <typename T> struct get_type;
      |                              ^~~~~~~~

which tells use first is an A, second is a B and third is an int as seen in this live example.

  • Related