Question

Assuming I have the following rule expecting a string value but should convert it to an integer:

integer %=
    attr_cast<int,std::string>(
    lexeme[
        -(char_('+') | char_('-')) >>
        char_("1-9") >> *char_("0-9")
    ]
    )
    ;

so I defined a transformation structure:

template <>
struct transform_attribute<int, std::string, qi::domain>
{
    // input type
    typedef std::string& type;

    static std::string pre(int & d) {
        return "";
    }
    static void post(int & val, const type attr) {
        val = boost::lexical_cast<int>(attr);
    }
    static void fail(int &) {}
};

unfortunately it expects to convert the int reference of the left rule into a string reference (I removed the reference operator but it does not work). On the Internet I saw examples working the other way int to string where they don't use references for the int. How can I define a proper transformation customly?

Was it helpful?

Solution

First off, you don't have to write this:

integer %= qi::int_; // Job Done

Next, you could write the qi::attr_cast<std::string>(...) easier and more efficient by doing as_string[ ... ].

Next, if you do want to parse ints the laborious way, try this:

bool ok = parse(f, l,
        (matches['-']  | -lit('+') >> attr(false)) [ phx::ref(negative) = _1 ] >> // sign
        eps    [ _val = 0 ] >>
        +digit [ _val *= 10, _val += (_1 - '0') ],
        parsed);

See a live demo on Coliru testing for (u)int(8,16,32,64,max)_t on the whole range:

The qi::int_parser<> template used above (via qi::int_) is essentially a generalization of this approach, but more efficient.

Now, you can of course do the traits trick if you insist:

namespace boost { namespace spirit { namespace traits { 

    template <typename Int>
        struct assign_to_attribute_from_value<
            Int, 
            std::string,
            typename std::enable_if<std::is_integral<Int>::value, void>::type // Enabler
            >
        {
            static void call(std::string const& val, Int& attr) {
                //std::cout << __PRETTY_FUNCTION__ << "('" << val << "')\n";
                attr = boost::lexical_cast<Int>(val);
            }
        };

} } }

Now this would be shooting a fly with a canon. Never mind that boost::lexical_cast doesn't handle uint8_t and int8_t correctly for this purpose (treating them specially as char and unsigned char?), so I had to also hard code exception for these:

// boost lexical_cast does not usefully support `char` types as integrals... (SIC)
template <>
    struct assign_to_attribute_from_value<signed char, std::string> {
        static void call(std::string const& val, signed char& attr) {
            int tmp;
            assign_to_attribute_from_value<int, std::string>::call(val, tmp);
            attr = static_cast<signed char>(tmp);
        }
    };

template <>
    struct assign_to_attribute_from_value<unsigned char, std::string> {
        static void call(std::string const& val, unsigned char& attr) {
            unsigned int tmp;
            assign_to_attribute_from_value<unsigned int, std::string>::call(val, tmp);
            attr = static_cast<unsigned char>(tmp);
        }
    };

Now all the test cases passed with

    Int parsed = 0;
    bool ok = parse(f, l, as_string [ -char_("-+") >> +digit ], parsed);

See it Live On Coliru as well.

Now let me conclude with the only "sane" approach: don't reinvent the wheel

 Int parsed = 0;
 bool ok = qi::parse(f, l, qi::auto_, parsed);

Full Program Live On Coliru

#include <boost/spirit/include/qi.hpp>

template <typename Int>
void do_test() {
    for (Int const testcase : { std::numeric_limits<Int>::min(), Int(), std::numeric_limits<Int>::max() }) {
        auto const input = std::to_string(testcase);
        auto f(input.begin()), l(input.end()); 

        Int parsed = 0;
        bool ok = boost::spirit::qi::parse(f, l, boost::spirit::qi::auto_, parsed);

        if (!ok || f!=l)
            throw std::runtime_error("parse error");

        std::cout << std::boolalpha << (testcase==parsed) << "\t" << testcase << " -> " << parsed << "\n";
    }
}

int main() {
    do_test<int16_t>();  do_test<uint16_t>();
    do_test<int32_t>();  do_test<uint32_t>();
    do_test<int64_t>();  do_test<uint64_t>();
    do_test<intmax_t>(); do_test<uintmax_t>();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top