0

[EDIT - I long since forgot this was here until I got the 2,500 views "notable question". Since people are viewing - there is useful information about overloads in the accepted answer, but specifically checking for std::endl is even worse than I realized at the time, and definitely the wrong thing.

Basically, the effect of std::endl is to output \n to the stream then flush with std::flush. That's irrespective of platform, including Windows where the end-of-line is really "\r\n". The endl manipulator doesn't abstract away platform differences WRT line ends, C++ handles that the same way that C does - by translating \n to "\r\n" (for text mode, not binary) later on. I thought C++ was doing something different, an assumption so strong I never even questioned it for 2 decades, but I was wrong.

I don't remember details, but it's possible to define your own streams anyway, and provide alternative output (and translation) of whatever characters are streamed in. All manipulators should work as expected, before your custom stream code sees the resulting output characters. So to provide special end-of-line behaviour, watch for the \n there (which is still before the text-file end-of-line translation). ]

It's hackish, I know, but I recently needed to implement a stream class which would act mostly like a standard stream, but which would detect the std::endl manipulator and special-case it's behaviour. My first attempt at a particular method implementation was...

mystream& mystream::operator<< (std::basic_ostream<char>& (*p) (std::basic_ostream<char>&))
{
  if (p == &std::endl)
  {
    //  Handle special case
  }
  else
  {
    m_Underlying_Stream << p;
  }

  return *this;
}

The trouble with this is that the compiler doesn't know which overload of std::endl I'm referring to. I resolved it as follows...

mystream& mystream::operator<< (std::basic_ostream<char>& (*p) (std::basic_ostream<char>&))
{
  typedef std::basic_ostream<char>& (*ENDL_T) (std::basic_ostream<char>&);

  const ENDL_T l_ENDL (&std::endl);

  if (p == l_ENDL)
  {
    //  Handle special case
  }
  else
  {
    m_Underlying_Stream << p;
  }

  return *this;
}

That is the compiler can resolve the overload in the context of an initialisation (and for assignment too, as another experiment proved), but not for operator==.

The compiler in question is MinGW GCC 4.4.0, but I don't think this is likely to be a compiler issue.

I had a look around and found this question...

How to get the address of an overloaded member function?

If my code has a const issue, I don't know where the missing const needs to go. I can't see any other obvious type issue.

I have some vague ideas about number-of-steps issues WRT overloading or implicit casting, but nothing concrete. So - can anyone explain clearly what is wrong with my first example, why the second version fixes it, and how I can safely indicate which overload I mean when taking the address of a function.

BTW - I can guess some people won't like me testing directly for the address of std::endl, and will point out that this is fragile - e.g. someone could have their own manipulator which calls std::endl which I wouldn't spot. In general this is true, but in this special case, the hack saves a lot of time and the nastiness just doesn't matter.

Community
  • 1
  • 1

3 Answers3

2

The use of an overloaded function name, (or the name of a function template which behaves like a set of overloaded functions) without arguments (such as in an "address of" expression) is only allowed in a limited set of contexts where the context can be used to uniquely determine the particular overload required.

This is specified in 13.4 of the standard (ISO/IEC 14882:2003) [over.over]. Included are an initializer for an object or reference or in an explicit conversion. This gives you a number of options.

E.g. explicit conversion:

typedef std::ostream& (*ManipPtr)(std::ostream&);

mystream& mystream::operator<<(ManipPtr p)
{
    if (p == static_cast<ManipPtr>(&std::endl))
    {
        // ...

Directly initializing a pointer:

typedef std::ostream& (*ManipPtr)(std::ostream&);

mystream& mystream::operator<<(ManipPtr p)
{
    const ManipPtr pEndl = &std::endl;

    if (p == pEndl)
    {
        // ...
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • Actually, on second thoughts, I'm still not clear on why `operator<<` can provide adequate context but `operator==` cannot. Is there ambiguity over which overload of `operator==` to use, for instance? –  Jul 27 '10 at 21:01
  • Could this difference be because `operator<<` on streams is a member function but `operator==` on function pointers is just a standard non-member function? –  Jul 27 '10 at 21:12
  • @Steve314: One of the other contexts that can be used is "a parameter of a function" and `operator<<` for streams is actually a function so this is allowed. In general this could be ambiguous, e.g. if multiple `op<<` took function pointers of different types and multiple overloads matched the respective `op<<`. In this case it's OK, though. – CB Bailey Jul 27 '10 at 21:16
1

The following works:

#include <iostream>

struct mystream {
    typedef std::basic_ostream<char>& (*ENDL_T) (std::basic_ostream<char>&);
    mystream& operator<< (ENDL_T p)
    {
        if (p == (ENDL_T)std::endl)
        {
            std::cout << "special case\n";
        }
        else
        {
            std::cout << "usual case\n";
        }
        return *this;
    }
};

int main()
{
    mystream ms;
    ms << std::endl; // prints "special case"
    ms << std::flush; // prints "usual case"
}
Cubbi
  • 46,567
  • 13
  • 103
  • 169
  • 1
    ...or static_cast, as per @Charles Bailey. By the way, this kind of overload resolution is something every textbook runs into when implementing `transform(s.begin(), s.end(), s.begin(), (int(*)(int)) toupper)` for strings. – Cubbi Jul 27 '10 at 20:51
0

The reason it can't distinguish the overloads is because you're resolving the address of the function, not calling it. When you insert it in the stream, the compiler knows to call the endl(ostream&) overload. Other than that, you're on your own.

Why not just test for '\n' instead?

Mike Caron
  • 14,351
  • 4
  • 49
  • 77
  • 2
    That would be different from his intended behavior; `std::endl` is **not** the same thing as '\n'. – acanaday Jul 27 '10 at 20:27
  • why should I be unable to look at a functions address, just because the function is overloaded? And please note - I *am* doing that successfully when I use the intermediate variable. –  Jul 27 '10 at 20:29
  • @Steve314: Usually an overloaded function is resolved by examining the argument list and picking the best match according the supplied arguments. When taking a function's address there is no argument list so this is not an option. In order to get around this the language specificies a limited set of context where the function name can be used and resolved to the correct overload by the context in which it is used. In a `==` expression isn't such a context. – CB Bailey Jul 27 '10 at 20:58