4
\$\begingroup\$

I thought it might be useful if reading could at least sort of mirror writing. For example, if I write some output like:

somefile << "foo: " << foo << ", bar: " << bar;

...it would be nice if I could read it back in like:

somefile >> "foo: " >> foo >> ", bar: " >> bar;

So, this code attempts to support that:

#ifndef FMT_READ_H_
#define FMT_READ_H_
#include <cctype>
#include <iostream>
#include <locale>

template <class charT>
std::basic_istream<charT> &operator>>(std::basic_istream<charT> &is, charT const *fmt) {
    if (fmt == nullptr)
        return is;

    if (is.flags() & std::ios_base::skipws) {
        std::locale const &loc = is.getloc();
        if (std::has_facet<std::ctype<charT>>(loc)) {
            auto const &ct = std::use_facet<std::ctype<charT>>(loc);
            while (ct.is(std::ctype_base::blank, is.peek()))
                is.ignore(1);
        }
        else
            while (std::isspace(is.peek()))
                is.ignore(1);
    }

    while (*fmt) {
        if (*fmt != is.peek())
            is.setstate(std::ios_base::failbit);
        ++fmt;
        is.ignore(1);
    }
    return is;
}

#endif

Here's a little bit of test code:

#include <sstream>
#include <iostream>
#include "fmt_read.hpp"

int main() { 
    std::istringstream b("START(0, 0)\nGOAL(1,2)");

    int startX, startY;
    b >> "START(" >> startX >> "," >> startY >> ")";
    std::cout << "start_x: " << startX << ", start_y: " << startY << "\n";

    int goalX, goalY;
    b >> "GOAL(" >> goalX >> "," >> goalY >> ")";
    std::cout << "goal_x: " << goalX << ", goal_y: " << goalY << "\n";
}

One big question in my mind is about what it does if it finds that the stream has been imbued with locale that doesn't contain a ctype facet. Right now, in that case it falls back to the global locale--but I'm wondering whether it might be better to treat that as an error instead (but even then, not sure if it should set the failbit, or maybe throw an exception, or...)

\$\endgroup\$
  • \$\begingroup\$ Seems to only ignore leading white space. If I had a format of "Poo Paa" don't you think the internal space should match multiple space on the input stream. This is how space works for the scanf() family of functions. \$\endgroup\$ – Martin York Jan 11 at 22:16
2
\$\begingroup\$

You should use a std::basic_istream<CharT>::sentry to skip the whitespace.

Also, basic_istream takes a second template parameter, which you could also implement.

And you might want to rethink your design: Do you really want to skip whitespace?

Three possible scenarios I can think of:

  1. Always skip white space if the std::ios_base::skipws bit is set
  2. Only skip white space if the std::ios_base::skipws bit is set and fmt != nullptr
  3. Never skip white space (Be an UnformattedInputFunction)

Whatever the case, your code should look more like this:

template <class CharT, class Traits>
std::basic_istream<CharT, Traits> &operator>>(std::basic_istream<CharT, Traits> &is, CharT const *fmt) {
    typedef typename std::basic_istream<CharT, Traits>::sentry sentry_t;

// Case 1: Always skip
    sentry_t s(is);

    if (fmt == nullptr)
        return is;

// Case 2: Skip if there is a fmt

    if (fmt == nullptr)
        return is;

    sentry_t s(is);

// Case 3: Never skip

    if (fmt == nullptr)
        return is;

    sentry_t s(is, false);

// And also consider some edge cases: If EOF is reached, and we tried to read `""`, should we set failbit?

    // Check the sentry before using the stream
    if (s) {
        while (*fmt) {
            // is.peek() returns Traits::int_type. Convert first before comparing.
            if (!Traits::eq_int_type(Traits::to_int_type(*fmt), is.peek()))
                is.setstate(std::ios_base::failbit);  // Why don't you return here?
            ++fmt;
            // If an error occurred when ignoring the character, stop reading
            if (!is.ignore(1)) {
                return is;
            }
        }
    }
    return is;
}

Also, currently your solution is very undefined. When overloading an operator, at least one of the parameters must be not-builtin and not-stl (What if someone else overloaded the same operator but completely unrelated to you?)

A simple fix would be to make it a function instead.

You can carry on with your solution (As there probably won't be a conflict) but I would suggest doing it like this:

namespace my_namespace {
    inline namespace operators {
        template <class CharT, class Traits>
        std::basic_istream<CharT, Traits> &operator>>(std::basic_istream<CharT, Traits> &is, CharT const *fmt) {
            // ...
        }
    }
}

// And explicitly "enable" it in functions by using it
int main() {
    // one of:
    using namespace my_namespace;
    using namespace my_namespace::operators;
    using my_namespace::operator>>;
    using my_namespace::operators::operator>>;

    // And do this in every function you use this.
    // Note that this is still UB but less likely
    // to be unable to compile.
}
\$\endgroup\$
  • \$\begingroup\$ It would be even better to create a small struct in that namespace, so ADL could slam in. \$\endgroup\$ – Incomputable Jan 10 at 17:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.