14

My understanding is that reading a uint8_t from a stringstream is a problem because the stringstream will interpret the uint8_t as a char. I would like to know how I can read a uint8_t from a stringstream as a numeric type. For instance, the following code:

#include <iostream>
#include <sstream>

using namespace std;

int main()
{
    uint8_t ui;
    std::stringstream ss("46");
    ss >> ui;
    cout << unsigned(ui);
    return 0;
}

prints out 52. I would like it to print out 46.

EDIT: An alternative would to just read a string from the stringstream and then convert the solution to uint8_t, but this breaks the nice chaining properties. For example, in the actual code I have to write, I often need something like this:

   void foobar(std::istream & istream){
       uint8_t a,b,c;
       istream >> a >> b >> c;
       // TODO...
   }
bremen_matt
  • 6,902
  • 7
  • 42
  • 90

3 Answers3

8

You can overload the input operator>> for uint8_t, such as:

std::stringstream& operator>>(std::stringstream& str, uint8_t& num) {
   uint16_t temp;
   str >> temp;
   /* constexpr */ auto max = std::numeric_limits<uint8_t>::max();
   num = std::min(temp, (uint16_t)max);
   if (temp > max) str.setstate(std::ios::failbit);
   return str;
}

Live demo: https://wandbox.org/permlink/cVjLXJk11Gigf5QE

To say the truth I am not sure whether such a solution is problem-free. Someone more experienced might clarify.


UPDATE

Note that this solution is not generally applicable to std::basic_istream (as well as it's instance std::istream), since there is an overloaded operator>> for unsigned char: [istream.extractors]. The behavior will then depend on how uint8_t is implemented.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • This looks nice. If you put it in an anonymous namespace, then I imagine that any damage would be limited – bremen_matt Feb 11 '19 at 10:05
  • 1
    And although I just asked about stringstreams, I guess you could easily change that `stringstream` to `istream`, and then are covered for all input streams. – bremen_matt Feb 11 '19 at 10:10
  • 1
    @bremen_matt It doesn't seem to be so simple. Look here: https://wandbox.org/permlink/8a2kGAywWyJUfPzr. The compiler obviously gives precedence to the _simple implicit conversion_ of `ui` to `char` before pairing `str` with `ss` by _derived-to-base_ conversion. Not sure how to solve this. – Daniel Langr Feb 11 '19 at 10:18
  • `std::istream& operator>>(std::istream& str, uint8_t& num)`... Forget the template and `#include ` – bremen_matt Feb 11 '19 at 10:27
  • Btw, I had never heard of Wandbox. I love how you can switch the compiler like that... – bremen_matt Feb 11 '19 at 10:28
  • @bremen_matt Yes, just then it will not cover _all input streams_. But you are likely fine with that. – Daniel Langr Feb 11 '19 at 10:29
  • @bremen_matt I like it so much for simple experiments. Especially because it supports Vim-like source editing, which I havne't seen in other online IDEs (like in popular IdeOne or Coliru). – Daniel Langr Feb 11 '19 at 10:30
  • `num = std::min(temp, (uint16_t)max);` – This has an implicit conversion, which is not so nice (and neither is the C-style cast). I will admit though that `num = static_cast(std::min(temp, static_cast(max)));` is not elegant either. Maybe move the assignment into the `if` rsp. a newly created `else` branch. – Arne Vogel Feb 11 '19 at 12:39
  • 2
    @DanielLangr "The compiler obviously gives precedence to the simple implicit conversion of `ui` to char." – This kind of implicit conversion does not apply to references. There is an `unsigned char &` overload for the `std::operator>>` for streams however. I'm not sure what causes the apparent ambiguity to be resolved in favor of the `std` version but highly suspect the following specialization in libstdc++: `extern template istream& operator>>(istream&, unsigned char&);` – It goes to show that it's a really bad idea to make ambiguous overloads of standard functions, I guess. – Arne Vogel Feb 11 '19 at 12:54
  • @ArneVogel Agree, I would better use only a version where no conversion is required in this case, such as the shown one for `std::stringstream`. – Daniel Langr Feb 11 '19 at 13:00
  • 1
    @ArneVogel Ooh, see your point now. I didn't mention the overloads for `unsigned char` before (http://eel.is/c++draft/input.streams#istream.extractors). This solution might be really fragile. I will put a note into the answer about it. – Daniel Langr Feb 11 '19 at 13:06
3

Please do not use char or unsigned char(uint8_t) if you want to read in a formatted way. Your example code and its result is an expected behavior.

As we can see from https://en.cppreference.com/w/cpp/io/basic_istream/operator_gtgt2

template< class Traits >
basic_istream<char,Traits>& operator>>( basic_istream<char,Traits>& st, unsigned char& ch );

This does "Performs character input operations".

52 is an ascii code for '4'. Which means that the stringstream has read only one byte and still ready to read '6'.

So if you want work in the desired way, you should use 2-byte or bigger integer types for sstream::operator>> then cast it to uint8_t - the exact way that you self-answered.

Here's a reference for those overloads. https://en.cppreference.com/w/cpp/io/basic_istream/operator_gtgt

Hanjoung Lee
  • 2,123
  • 1
  • 12
  • 20
2

After much back and forth, the answer seems to be that there is no standard way of doing this. The options are to either read off the uint8_t as either a uint16_t or std::string, and then convert those values to uint8_t:

#include <iostream>
#include <sstream>

using namespace std;

int main()
{
    uint8_t ui;
    uint16_t tmp;
    std::stringstream ss("46");
    ss >> tmp;
    ui = static_cast<uint8_t>(tmp);
    cout << unsigned(ui);
    return 0;
}

However, such a solution disregards range checking. So you will need to implement that yourself if you need it.

bremen_matt
  • 6,902
  • 7
  • 42
  • 90