views:

70

answers:

3

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...

http://stackoverflow.com/questions/705854/c-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.

A: 

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
That would be different from his intended behavior; `std::endl` is **not** the same thing as '\n'.
acanaday
@Mike - 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.
Steve314
@acanaday - absolutely correct.
Steve314
@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.
Charles Bailey
+1  A: 

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
...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
@Cubbi - interesting point about `transform`.
Steve314
+2  A: 

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)
    {
        // ...
Charles Bailey
Thanks - looks like a perfect answer.
Steve314
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?
Steve314
Could this difference be because `operator<<` on streams is a member function but `operator==` on function pointers is just a standard non-member function?
Steve314
@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.
Charles Bailey