3

I have an input vector that can have any size between empty and 3 elements. I want the generated string to always be 3 floats separated by spaces, where a default value is used if there aren't enough elements in the vector. So far I've managed to output only the contents of the vector:

#include <iostream>
#include <iterator>
#include <vector>

#include "boost/spirit/include/karma.hpp"

namespace karma = boost::spirit::karma;
namespace phx   = boost::phoenix;
typedef std::back_insert_iterator<std::string> BackInsertIt;

int main( int argc, char* argv[] )
{
    std::vector<float> input;
    input.push_back(1.0f);
    input.push_back(2.0f);

    struct TestGram 
        : karma::grammar<BackInsertIt, std::vector<float>(), karma::space_type>
    {
        TestGram() : TestGram::base_type(output)
        {
            using namespace karma;
            floatRule = double_;

            output = repeat(3)[ floatRule ];
        }

        karma::rule<BackInsertIt, std::vector<float>(), karma::space_type> output;
        karma::rule<BackInsertIt, float(), karma::space_type> floatRule;
    } testGram;


    std::string output;
    BackInsertIt sink(output);
    karma::generate_delimited( sink, testGram, karma::space, input );

    std::cout << "Generated: " << output << std::endl;

    std::cout << "Press enter to exit" << std::endl;
    std::cin.get();
    return 0;
}

I've tried modifying the float rule to something like this: floatRule = double_ | lit(0.0f), but that only gave me compilation errors. The same for a lot of other similar stuff I tried.

I really have no idea how to get this working. Some help would be great :)

EDIT: Just to make it clear. If I have a vector containing 2 elements: 1.0 and 2.0, I want to generate a string that looks like this: "1.0 2.0 0.0" (the last value should be the default value).

Krienie
  • 611
  • 1
  • 6
  • 14

2 Answers2

2

Not pretty, but working:

#include <iostream>
#include <iterator>
#include <vector>
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include "boost/spirit/include/karma.hpp"
#include <boost/spirit/include/phoenix.hpp>

namespace karma = boost::spirit::karma;
namespace phx = boost::phoenix;
typedef std::back_insert_iterator<std::string> BackInsertIt;

int main(int argc, char* argv[]) {
  std::vector<float> input;
  input.push_back(1.0f);
  input.push_back(2.0f);

  struct TestGram: karma::grammar<BackInsertIt, std::vector<float>(),
      karma::space_type> {
    TestGram()
        : TestGram::base_type(output) {
      using namespace karma;
      floatRule = double_;

      output = repeat(phx::bind(&std::vector<float>::size, (karma::_val)))[floatRule]
            << repeat(3 - phx::bind(&std::vector<float>::size, (karma::_val)))[karma::lit("0.0")];
    }

    karma::rule<BackInsertIt, std::vector<float>(), karma::space_type> output;
    karma::rule<BackInsertIt, float(), karma::space_type> floatRule;
  } testGram;

  std::string output;
  BackInsertIt sink(output);
  karma::generate_delimited(sink, testGram, karma::space, input);

  std::cout << "Generated: " << output << std::endl;

  return 0;
}
Mike M
  • 2,263
  • 3
  • 17
  • 31
  • I think I've come up with something a little more palatable. However, I really think your data type should probably be something like `struct Vec3 { float a, b; optional c; };` – sehe Sep 03 '13 at 11:33
  • 1
    @sehe I'd rather not change the data type, because of the context this is used in. If I do, I'd have to change almost half of my pipeline.. – Krienie Sep 03 '13 at 12:54
1

Big warning:

The code shown is flawed, either due to a bug, or due to abuse of karma attribute propagation (see the comment).

It invokes Undefined Behaviour (presumably) dereferencing the end() iterator on the input vector.

This should work

    floatRule = double_ | "0.0";

    output = -floatRule << -floatRule << -floatRule;

Note, floatRule should accept an optional<float> instead. See it Live on Coliru

Minimal example:

#include "boost/spirit/include/karma.hpp"

namespace karma = boost::spirit::karma;
using It = boost::spirit::ostream_iterator;

int main( int argc, char* argv[] )
{
    const std::vector<float> input { 1.0f, 2.0f };

    using namespace karma;
    rule<It, boost::optional<float>()> floatRule      = double_ | "0.0";
    rule<It, std::vector<float>(), space_type> output = -floatRule << -floatRule << -floatRule;

    std::cout << format_delimited(output, space, input);
}

sehe
  • 374,641
  • 47
  • 450
  • 633
  • VS2012 does not like your solution unfortunately. I get a security warning (which I can disable to get it to compile by setting -D_SCL_SECURE_NO_WARNINGS, but I'd rather not as I have no idea what other bugs I might mask by doing that). The error is: `error C4996: 'std::_Copy_impl': Function call with parameters that may be unsafe` – Krienie Sep 03 '13 at 12:53
  • It means that it couldn't statically check the the iterators to a function/not use checked (SCARY?) iterators. It might be the vector iterators or, more likely, the `"0.0"` (try with `lit("0.0")`? Or, disable DEBUG build. (I don't have MSVC) – sehe Sep 03 '13 at 13:21
  • disabling DEBUG build does make it compile, but then output is a smilie face instead of 3 floats (not kidding). I've probably done something else wrong while adapting your example into my code, but it doesn't matter. I'll just keep using Mike's "ugly" code. Thanks for your effort anyways ;) – Krienie Sep 03 '13 at 14:21
  • 1
    @Krienie I just tested this with VS2010 and you're right. MSVC correctly flags invalid iterators. I'm mildly awed. I just verified with valgrind and indeed, it is the vector's iterators that cause the problem. All I can say, my solution only "appeared to work" because of UB. Whether or not this is a bug in Karma, I'm willing to forget for the moment. – sehe Sep 03 '13 at 23:48