2

following code gives me weird compiler error on VS2019 with c++17 mode:

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

class Duration
{
  std::chrono::duration<int64_t, std::nano> d;

public:
  void addHours(uint64_t val) noexcept
  {
  }
};


int main()
{
  Duration d;
  namespace spirit = boost::spirit;
  namespace phoenix = boost::phoenix;
  namespace qi = boost::spirit::qi;
  qi::int_parser<int64_t, 10> int64_;
  std::string s = "2h";
  boost::spirit::qi::rule<std::string::iterator, Duration()> durationRule = (int64_ >> "h")[phoenix::bind(&Duration::addHours, qi::_val, qi::_1)];

  boost::spirit::qi::phrase_parse(s.begin(), s.end(), durationRule, boost::spirit::ascii::space, d);
}

If I compile it with c++14 mode all is fine, but with c++17 or c++20 it fails. I found that the problem is the noexcept keyword behind the addHours method. Can anybody explain why this is a problem?

sehe
  • 374,641
  • 47
  • 450
  • 633

1 Answers1

1

The Phoenix library predates noexcept.

Since c++17 The noexcept-specification is a part of the function type and may appear as part of any function declarator.

The implication is that you run into limitations of Phoenix when using the noexcept specifications in c++17 mode.

Specifically, the thing that appears to break is return type deduction: Functions differing only in their exception specification cannot be overloaded (just like the return type, exception specification is part of function type, but not part of the function signature) (since C++17).

Improvements

In your case, since you're using C++17, why not simplify the whole thing? Live On Coliru

qi::rule<std::string::const_iterator, Duration()> durationRule //
    = qi::eps[_val = 0s]                                       //
    >> (qi::int_ >> "h")[_val += _1 * 1h];

The output

"2m" -> failed
"3h" -> 10800
"4s" -> failed

gives away my intent!

Bonus Extension 1: units

Using a simple table like

qi::symbols<char, Duration> unit_;
unit_.add
    ("ns", 1ns) ("nano", 1ns)
    ("us", 1us) ("μs", 1us) ("micro", 1us)
    ("ms", 1ms) ("milli", 1ms)
    ("s", 1s)
    ("m", 1min) ("min", 1min)
    ("h", 1h) ("hour", 1h)
    ("d", 24h) ("day", 24h)
    ;

Now with the trivial change of

    >> +(qi::int_ >> unit_)[_val += _1 * _2];

It does evaluates a boatload of interesting sequences: Live On Coliru

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <chrono>
#include <iomanip>

using namespace std::chrono_literals;
using Duration = std::chrono::duration<int64_t, std::nano>;
namespace qi   = boost::spirit::qi;

int main()
{
    using namespace qi::labels; // _val, _1 etc

    qi::symbols<char, Duration> unit_;
    unit_.add
        ("ns", 1ns) ("nano", 1ns)
        ("us", 1us) ("μs", 1us) ("micro", 1us)
        ("ms", 1ms) ("milli", 1ms)
        ("s", 1s)
        ("m", 1min) ("min", 1min)
        ("h", 1h) ("hour", 1h)
        ("d", 24h) ("day", 24h)
        ;

    qi::int_parser<int64_t, 10> int64_;
    qi::rule<std::string::const_iterator, Duration(), qi::blank_type>
        durationRule         //
        = qi::eps[_val = 0s] //
        >> +(int64_ >> unit_)[_val += _1 * _2];

    for (std::string const s :
         {
             "2m",
             "3h",
             "4s",
             "1ms -1ns",
             "1 day -23h",
             "3600000000ns",
             "1 day -23h -50m +3600000000ns",
         }) //
    {
        std::cout << std::quoted(s);

        Duration d{};
        if (phrase_parse(                //
                s.begin(), s.end(),      //
                durationRule >> qi::eoi, //
                qi::blank, d))
            std::cout << " -> " << d / 1.0s << "s\n";
        else
            std::cout << " -> failed\n";
    }
}

Prints

"2m" -> 120s
"3h" -> 10800s
"4s" -> 4s
"1ms -1ns" -> 0.000999999s
"1 day -23h" -> 3600s
"3600000000ns" -> 3.6s
"1 day -23h -50m +3600000000ns" -> 603.6s

Note I've shifted some details around to make it work. Notably qi::eoi to check the entire input is consumed, and declaring the skipper in the rule (blank instead of space to exclude newlines). Without that, the rule was implicitly lexeme anyways (Boost spirit skipper issues)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • As you can probably see from the heading in my answer, I was planning to add another version. Here's the Spirit X3 version: http://coliru.stacked-crooked.com/a/66c68cfb3d1a146e – sehe Sep 29 '21 at 12:46
  • I found two cases that don't work, but I also don't know why: "200µs" and "1m2ms". The first contaisn the special char, the secons runs into an ambiguity of m and ms I guess. – Steffen Roeber Oct 04 '21 at 20:47
  • Haha that's the difference between [U+00B5 MICRO SIGN](https://www.fileformat.info/info/unicode/char/00b5/index.htm) and [U+03BC GREEK SMALL LETTER MU](https://www.fileformat.info/info/unicode/char/03bc/index.htm). I didn't know about the first one :) And the second one JustWorks™ for me: http://coliru.stacked-crooked.com/a/01812161a11f5019 (perhaps you changed the `symbols<>` to branches? In that case, you have to mind the order of the branches. `symbols<>` automatically matches the longest possible match from the trie) – sehe Oct 05 '21 at 09:31
  • Noticed that X3 requries switching the encoding for that character to be accepted: http://coliru.stacked-crooked.com/a/e125ca79750d8951 – sehe Oct 05 '21 at 10:16