2

I made a post about this earlier but that one did not have good explanation and therefore I deleted it, hopefully this one will be better. Right now I have two questions about the following code

#include <vector>
#include <sstream>
#include <iostream>
#include <cstdio>
#include <string>
using std::cerr;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::vector;

int main() {

    // the tests
    vector<string> tests {"1.2 when 102"s, "1.2 1.2 1.2"s};

    // format and the storage variables
    string format {"%d %4s %d"};
    int input_1 {-1};
    char char_arr[5];
    int input_2 {-1};

    for (const auto& str : tests) {
        cout << "Number of elements matched : ";
        cout << std::sscanf(str.c_str(), format.c_str(), &input_1, char_arr,
                &input_2) << endl;
        cout << input_1 << endl;
        cout << char_arr << endl;
        cout << input_1 << endl;
    }

    return 0;
}

When I compile the code on a Mac with clang (clang-703.0.29) I get the following error

test.cpp:16:41: error: no matching literal operator for call to 'operator""s' with
      arguments of types 'const char *' and 'unsigned long', and no matching literal
      operator template

I thought user defined string literals were fully implemented in C++14. Why then does this code not compile? I may be doing something very stupid here...

If I run my code after removing the s after the literals then I get the following output

Number of elements matched : 2
1
.2
1
Number of elements matched : 3
1
.2
1

Why is input_2 a 1 in the first case? It should not be matched correctly and if it isn't then why is it a 1 and not a -1?

Also if I want to consider a floating point number as invalid in a sscanf call then which escape character or flag should I put in the format string?

Curious
  • 20,072
  • 7
  • 51
  • 128

2 Answers2

1

Your s string literals are not working because that operator resides in namespace std::literals::string_literals. Adding the appropriate using directive addresses this.

I don't believe what you're asking for is possible using sscanf et al, but if you're looking for an efficient, compact way to do this parsing and consider a floating point number as invalid, then I suggest Boost.Spirit. Here's a quick attempt using Spirit.X3:

#include <tuple>
#include <string>
#include <vector>
#include <iostream>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>

int main()
{
    using namespace std::string_literals;
    namespace x3 = boost::spirit::x3;

    auto const format =
        x3::int_ >> ' ' >> x3::repeat(4)[x3::print] >> ' ' >> x3::int_ >> x3::eoi;

    std::vector<std::string> const tests{"1.2 when 102"s, "1.2 1.2 1.2"s,
                                         "12 when 142"s,  "12 foo 142"s};
    for (auto const& str : tests)
    {
        int input_1 = -1;
        std::string chars;
        int input_2 = -1;

        auto attr = std::tie(input_1, chars, input_2);
        auto const success = x3::parse(cbegin(str), cend(str), format, attr);
        std::cout
            << '"' << str << "\" :: parse " << (success ? "succeeded" : "failed") << '\n'
            << input_1 << '\n'
            << chars << '\n'
            << input_2 << "\n\n";
    }
}

Online Demo

This parser is very strict – it expects exactly one space between fields, expects exactly four printable characters for the string field, and allows no leading or trailing whitespace. All of these requirements can be relaxed extremely trivially with Spirit, with compile-time safety if mistakes are made rather than UB/memory corruption at runtime.


EDIT: With Spirit it's even possible to avoid copying the data for the string field, unlike with sscanf et al, as long as the input string data is stable:

#include <tuple>
#include <string>
#include <vector>
#include <iostream>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/spirit/home/x3.hpp>

int main()
{
    using namespace std::string_literals;
    namespace x3 = boost::spirit::x3;

    auto const format =
        x3::int_ >> ' ' >> x3::raw[x3::repeat(4)[x3::print]] >> ' ' >> x3::int_ >> x3::eoi;

    std::vector<std::string> const tests{"1.2 when 102"s, "1.2 1.2 1.2"s,
                                         "12 when 142"s,  "12 foo 142"s};
    for (auto const& str : tests)
    {
        int input_1 = -1;
        boost::iterator_range<std::string::const_iterator> chars;
        int input_2 = -1;

        auto attr = std::tie(input_1, chars, input_2);
        auto const success = x3::parse(cbegin(str), cend(str), format, attr);
        std::cout
            << '"' << str << "\" :: parse " << (success ? "succeeded" : "failed") << '\n'
            << input_1 << '\n'
            << chars << '\n'
            << input_2 << "\n\n";
    }
}

Online Demo

ildjarn
  • 60,915
  • 9
  • 122
  • 205
  • Your text says 'in namespace `std::literals::string_literals`'; your code says `using namespace std::string_literals;` — which is correct? – Jonathan Leffler May 02 '16 at 01:01
  • @JonathanLeffler : Both – `std::literals::string_literals` is an inline namespace. (`std::literals` is too, but I'm not about to suggest anyone do `using namespace std;` ;-]) – ildjarn May 02 '16 at 01:04
  • What does the const after the vector mean? – Curious May 02 '16 at 04:19
  • @Curious : `std::vector const tests{...};` is semantically identical to `const std::vector tests{...};`. See [C++ Const Usage Explanation](http://stackoverflow.com/q/5598703/636019) – ildjarn May 02 '16 at 04:30
0

%d matches an integer. "1.2" isn't an integer, so %d matches just the "1" part. Everything goes off the rails from this point forward.

Sam Varshavchik
  • 98,597
  • 5
  • 74
  • 125
  • Thank you for your answer! I sort of figured that out.. The big problem was "Also if I want to consider a floating point number as invalid in a sscanf call then which escape character or flag should I put in the format string?" – Curious May 01 '16 at 22:41
  • 2
    The right answer is "don't use sscanf". It's a C library function, not C++. If you want to parse a string a certain way, the best way to do that is to write your own code to parse the string, one character at a time, exactly in the manner you need to parse it. – Sam Varshavchik May 01 '16 at 22:43
  • The C++ alternative of using a stringstream has so much overhead though. For example, not having a constructor that accepts an rvalue reference to a string. Even the `.str()` function. That's why I am trying to use `sscanf()`, also using `std::sscanf()` makes me feel slightly better :) – Curious May 01 '16 at 22:46
  • 1
    You are absolutely right. However, I said nothing about using `std::stringstream` in my comment. – Sam Varshavchik May 01 '16 at 22:47
  • I would also add that to actually parse the numbers you should use `strtol` and `strtod`, don't ever try parsing doubles one char at a time, because it's hard to get right. – Yakov Galka Jun 22 '16 at 22:09