How operator<< works with manipulator functions

Viewed 87

In C++ is possible to write code like this:

cout << std::endl;

where, std::endl is a function that will be called with cout as a parameter. I'm wondering is it special feature of << and >> operators or there is a generic rule that allows to do same thing with other operators (for example with +)?

3 Answers

There is nothing really special about this. std::ostream::operator<< is simply overloaded to accept various types in input.

In the case of manipulator functions, operator<< is overloaded to accept certain types of function pointers as input by default:

basic_ostream& operator<<(
    std::ios_base& (*func)(std::ios_base&) );

basic_ostream& operator<<(
    std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&) );

basic_ostream& operator<<(
    std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) );

And then the implementations of those overloads just call whatever function is passed in to them, specifying *this as the function's input parameter, eg:

basic_ostream& basic_ostream::operator<<(std::ios_base& (*func)(std::ios_base&) )
{
    (*func)(*this);
    return *this;
}

basic_ostream& basic_ostream::operator<<(std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&))
{
    (*func)(*this);
    return *this;
}

basic_ostream& basic_ostream::operator<<(std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&))
{
    (*func)(*this);
    return *this;
}

Thus cout << std::endl; is simply calling cout.operator<<(std:endl);, where std::endl() matches the 3rd overload above:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

In the case of the stream manipulator functions in the <iomanip> header, the manipulators that do not take any input parameters from user's code (ie std::boolalpha, std::showbase, etc) are usually implemented as standalone functions that match one of the overloads above, and thus DO get passed the ostream/istream directly as a parameter. However, the manipulators that take input parameters from user code (ie std::resetiosflags(), std::setbase(), etc) are defined to return "unspecified" (ie implementation-defined) types, where their implementations define additional overloads of operator<< and operator>> for those types so they can receive the ostream/istream to manipulate, respectively.

For example: std::fill might be implemented something like this:

template< class CharT >
struct fill_t { CharT ch; };

template< class CharT >
fill_t setfill( CharT c )
{
    return fill_t<CharT>{c};
}

template< class CharT >
basic_ostream& operator<<(basic_ostream& out, const fill_t &in)
{
    out.fill(in.ch);
    return out;
}

Thus cout << setfill(' ') calls the equivalent of fill_t tmp = setfill(' '); operator<<(cout, tmp);

There is nothing special going on here. std::istream and std::ostream have overloads for the operator >> and operator << respectively in the form of

basic_istream& operator>>( std::ios_base& (*func)(std::ios_base&) );
basic_istream& operator>>( std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&) );
basic_istream& operator>>( basic_istream& (*func)(basic_istream&) );

basic_ostream& operator<<( std::ios_base& (*func)(std::ios_base&) );
basic_ostream& operator<<( std::basic_ios<CharT,Traits>& (*func)(std::basic_ios<CharT,Traits>&) );
basic_ostream& operator<<( std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) );

that allow the class to take a function pointer as the right hand side of the operator. When doing

cout << std::endl;

std::endl decays into a function pointer and the correct overload is called. You can do this with any operator for any type you have.


If you do plan to do this, think about the principal of least surprise, i.e don't use a common syntax that has a completely different meaning and use that as your custom overload.

You sure can.

#include <iostream>

struct Foo
{
   int f = 0;
};

Foo operator+(Foo foo, int (*function)())
{
   return {foo.f + function()};
}

int one()
{
   return 1;
}

int main(int argc, const char* argv[])
{
   Foo foo {10};
   std::cout << (foo+one).f << std::endl;
}

Output:

11
Related