The problem:
- Some types, like
cstdint
aliases or, at least purely theoretically, even std::size_t
, may (but don’t have to) be bond to char
, signed char
or unsigned char
. In practise std::int_least8_t
seems to be often bond to signed char
and std::uint_least8_t
– to unsigned char
. This seems odd, since semantically, all those types are supposed to represent integers rather than characters.
- In case of variables of a character type, the
>>
operator writes the ASCII code of the first non-whitespace character of the input stream into the variable, while the <<
operator outputs a character of the ASCII code equal to the value of the variable.
- Therefore it may happen that if we, for example, declare a variable as
int_least8_t
and use the input operator or the output operator on it, then the results will be far from expected. This seems to be even more awkward since whether such aliases are bond to character types or not depends only on the implementation and is a priori unknown.
An example of the above problems: http://ideone.com/JgyFhR A proof that purely theoretically, this can even be the case of std::size_t
: http://stackoverflow.com/questions/32915434/is-it-guaranteed-that-size-t-vectorsize-type-etc-typedefs-wont-bind-to-a-ch
I’ve attempted to write a generic solution to all those problems:
#ifndef INTIO_H
#define INTIO_H
#include <istream>
#include <ostream>
#include <type_traits>
#include <limits>
template
<class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
class integer_IO
{
T &val;
public:
integer_IO(T &arg) : val(arg) {}
integer_IO(T &&arg) : val(arg) {}
friend std::istream &operator>> (std::istream &is, integer_IO<T> &&i)
{
using TT = decltype(+i.val);
TT hlp;
is >> hlp;
TT constexpr minval = static_cast<TT>(std::numeric_limits<T>::min());
TT constexpr maxval = static_cast<TT>(std::numeric_limits<T>::max());
i.val = static_cast<T>(hlp > maxval ? maxval : hlp < minval ? minval : hlp);
if(hlp > maxval || hlp < minval)
is.setstate(std::ios::failbit);
return is;
}
friend std::ostream &operator<< (std::ostream &os, integer_IO<T> const &&i)
{
os << +i.val;
return os;
}
};
template
<class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
integer_IO<T> intIO(T &arg)
{
return integer_IO<T>(arg);
}
template
<class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
integer_IO<T> intIO(T &&arg)
{
return integer_IO<T>(arg);
}
#endif
The usage of this wrapper is simple:
#include <iostream>
#include <cstdint>
#include "intIO.hpp"
using namespace std;
int twoplustwo()
{
return 2+2;
}
int main()
{
int_least8_t i;
cin >> intIO(i);
cout << intIO(i) << '\n';
cout << intIO(twoplustwo()) << '\n';
}
What is your opinion of my code?