ChrisMM
ChrisMM

Reputation: 10103

Ambiguity when inheriting ostringstream

I have a simple struct which inherits from std::ostringstream, in order to handle some values better for databases. If I just inherit, and add in a simple constructor to set widths for precision of doubles, it works fine.

struct myostream : std::ostringstream {
    myostream() noexcept( false ) {
        this->precision( 6 );
        this->setf( ios_base::fixed, ios_base::floatfield );
    }
};

Now, I'm trying to add in an operator<< for type double, in order to handle infinity, and NaN.

myostream &operator<<( double val ) {
    if ( std::isnan( val ) ) {
        *static_cast<std::ostringstream *>( this ) << "'NaN'";
    } else if ( std::isinf( val ) ) {
        *static_cast<std::ostringstream *>( this ) << ( val < 0 ? "'-Infinity'" : "'Infinity'" );
    } else {
        *static_cast<std::ostringstream *>( this ) << val;
    }
    return *this;
}

As soon as I add this function in, I just get ambiguous function calls everywhere, for every type (bool, char, char *, int, etc.).

2>...\include\myostream.hpp(13,14): message : could be 'myostream &myostream::operator <<(double)' (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\ostream(694,32): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)' (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\ostream(775,31): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)' (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\ostream(856,32): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,_Elem)'
2>        with
2>        [
2>            _Elem=char
2>        ] (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\ostream(898,31): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,signed char)' (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\ostream(909,31): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,unsigned char)' (compiling source file ...)
2>C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.30.30704\include\thread(275,26): message : or       'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,std::thread::id)' (compiling source file ...)
2>...: message : or       'built-in C++ operator<<(int, int)'
2>...: message : while trying to match the argument list '(myostream, char)'

So, my question is: how would I go about overriding operator<< for some types, without adding in ambiguity? Is this even possible, or would I need to make a function which returns a string instead?

Edit: Adding in using std::ostringstream::operator<<; just increases the number of ambiguities.

Upvotes: 1

Views: 130

Answers (2)

康桓瑋
康桓瑋

Reputation: 43166

When the type of T is not double such as char, it will be implicitly converted to double and call myostream::operator<<, but since ostream::operator<< has char overload, myostream will also be implicitly converted to ostream and call base's operator<<, so there is ambiguity here.

You can use SFINAE to prevent the implicit conversion of double in myostream::operator<< when T is not a double:

template<class T>
inline std::enable_if_t<std::is_same_v<T, double>, myostream&>
operator<<(myostream& os, T val ) {
  if ( std::isnan( val ) ) {
      static_cast<std::ostringstream &>( os ) << "'NaN'";
  } else if ( std::isinf( val ) ) {
      static_cast<std::ostringstream &>( os ) << ( val < 0 ? "'-Infinity'" : "'Infinity'" );
  } else {
      static_cast<std::ostringstream &>( os ) << val;
  }
  return os;
}

Demo.

Upvotes: 2

Ted Lyngmo
Ted Lyngmo

Reputation: 118087

Here's one option:

struct myostream : std::ostringstream {
    using std::ostringstream::operator<<;    // bring in all the member overloads
    
    myostream() noexcept( false ) {
        this->precision( 6 );
        this->setf( ios_base::fixed, ios_base::floatfield );
    }

    // call the base class member function explicitly in here:
    myostream &operator<<( double val ) {
        if ( std::isnan( val ) ) {
            std::ostringstream::operator<<("'NaN'");
        } else if ( std::isinf( val ) ) {
            std::ostringstream::operator<<( val < 0 ? "'-Infinity'" : "'Infinity'" );
        } else {
            std::ostringstream::operator<<(val);
        }
        return *this;
    }    
};

// front-end for all the free operator<< functions:
// (this could be narrowed down to not match on T:s that isn't supported)
template<class T>
myostream& operator<<(myostream& m, const T& x) {
    static_cast<std::ostringstream&>(m) << x;
    return m;
}

Upvotes: 2

Related Questions