Search code examples
c++c++11constexprnoexcept

Why exception specifiers on overloaded operators '<<' doesn't work to any std::ostream object, but does on those defined in the library?


exemplo.cpp:

#include <type_traits>
using std::is_same;

#include <utility>
using std::declval;

#include <iostream>
using std::ostream;
using std::cout;

struct Foo final {
    int value;
    inline constexpr Foo(const int i) noexcept : value{i} {};
    inline ~Foo() = default;
};

ostream& operator<<(ostream& out, const Foo& foo) noexcept { return out << foo.value; }

int main() {
    const Foo a(42);
    static_assert(is_same<decltype(cout), ostream>::value == true, ""); // assert always to true...

    static_assert(noexcept(cout << a) == true, ""); // assert to true if the operator on line 21 is defined noexcept(true)

    static_assert(noexcept(declval<ostream>() << a) == true, ""); // assert always to false...

    static_assert(noexcept(declval<decltype(cout)>() << a) == true, ""); // Same as line 32...

    return 0;
}

Compile command:

g++ -std=c++2a -fconcepts exemplo.cpp -o exemp.run

Errors:

exemplo.cpp:32:53: error: static assertion failed
    static_assert( noexcept( declval<ostream>() << a) == true, ""); // assert always to false...
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
exemplo.cpp:34:60: error: static assertion failed
    static_assert( noexcept( declval<decltype(cout)>() << a) == true, ""); // same as line 32
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~

The 'declval()' function "generates a pseudo compile time object" of a given type, even if this type isn't really contructible. So 'declval()' should produce another object like std::cout, in which the overloaded operator in line 21 should work at compile time (but it doesn't).

I realised that is also works to std::clog and std::cerr, both variable of ostream type.

It should just compile. I mean the exception especifier should be the same to any ostream object, not only for these three obviously.

Note: compiling with G++ 8.1.0; the flags on the image are not needed. Actually, no flag or only the flag -std=c++11 or greater may give the same output.


Solution

  • The reason is that declval generate a temp object, so if you code has another overload, like this

    ostream& operator<<(ostream&& out, const Foo& foo) noexcept { return out << foo.value; }
    

    it will work. Note that the overloaded function takes rvalue reference. I tested it with gcc 4.8.5 and -std=c++11.