Search code examples
c++c++11boostboost-spiritboost-spirit-karma

How to tel a boost::karma::rule not to consume its attribute without providing a valid generator?


Say we have the following source code:

#include <iostream>
#include <string>
#include <iterator>
#include <boost/spirit/include/karma.hpp>

namespace karma = boost::spirit::karma;

template <typename OutputIterator> struct grammar : karma::grammar<OutputIterator, std::nullptr_t()> {
  grammar() : grammar::base_type(query) {
    query =  "yeah";
  }

  karma::rule<OutputIterator, std::nullptr_t()> query;
};


int main(void) {
  typedef std::back_insert_iterator<std::string> iterator_type;
  std::string generated;
  iterator_type output_it(generated);
  //keys_and_values<sink_type> g;
  grammar<iterator_type> g;
  bool result = karma::generate(output_it, g, nullptr);
  std::cout << result << ":" << generated << std::endl;
  return 0;
}

This fails to compile because karma lacks some traits for std::nullptr_t (those are boost::spirit::traits::extract_c_string and boost::spirit::traits::char traits). More specifically, it fails because karma is not able to find a generator for an attribute of type std::nullptr_t.

I see several ways to cope with that:

  1. Replace std::nullptr_t by karma::unused_type in the grammar definition : It works on this example but may introduce ambiguity in a more complex grammar.
  2. Defining the traits specialization : In my opinion, this is dirty and not generic. Plus, it exposes my specialization of a standard type for everyone, leading to potential conflicts.
  3. Specializing an attribute transform : Same problem of specializing a standard type just for me.
  4. Write a custom generator : The best candidate so far, but it makes a serious load of highly templated code lines compared to the task complexity.
  5. Put a intermediate rule with a karma::unused_type attribute. A quick fix that works but have no sense.

Question : How can I tell the karma::rule to generate a simple literal and not care about having or not a generator for its attribute ?


Solution

  • You seem to have stumbled on the inverse of the infamous single-element fusion sequence conundrum[1] :(

    I noticed, because the error emanates from the code trying to verify that the input string matches the attribute (lit.hpp):

    // fail if attribute isn't matched by immediate literal
    typedef typename attribute<Context>::type attribute_type;
    
    typedef typename spirit::result_of::extract_from<attribute_type, Attribute>::type
        extracted_string_type;
    
    using spirit::traits::get_c_string;
    if (!detail::string_compare(
            get_c_string(
                traits::extract_from<attribute_type>(attr, context))
          , get_c_string(str_), char_encoding(), Tag()))
    {
        return false;
    }
    

    However, that makes no sense at all, since the docs state:

    lit, like string, also emits a string of characters. The main difference is that lit does not consumes [sic] an attribute. A plain string like "hello" or a std::basic_string is equivalent to a lit

    So I just... on a whim thought to coerce things a little, by using the same workaround that works for single-element fusion sequences on the Qi side:

    query = karma::eps << "yeah";
    

    And, voilà: it works: Live On Coliru


    [1] See

    Etc. This is a sad flaw that will probably need to be worked around for SpiritV2.