Re: Automatic invocation of conversion function: operator std::ost
 
Paul wrote:
"Tom Widmer [VC++ MVP]" wrote:
So, again, the behaviour I expected was:
1) N::Log(N::ERR): a temporary Log object is created; its constructor 
accepts, say, a single parameter indicating severity and locks the log file;
Ok.
2) the compiler discovers that the object finds itself to the left of ???<<??? 
operator that is not defined in this class;
Ok.
3) the compiler finds a conversion function and tries that: it returns 
std::ostream& for which the operator is defined;
It only does that if it finds a non-member function which the operator 
is defined. This conversion doesn't apply to member functions. The basic 
sequence is:
Lookup all functions named << using ordinary lookup. This first searches 
the class on the left for member operator<< functions, and then searches 
for non-member operator<< functions.
Next, ADL is done to find some more non-member functions, based on the 
namespaces of the arguments to the call.
Next, viable functions are found, e.g. ones where there are valid 
conversion sequences to the parameters.
Finally, the best viable function is chosen, if there is one.
Your code is failing because only member operator<< functions of your 
Log class are being found.
Not quite. According to Stroustrup (p. 267), "...in operator lookup no 
preference is given to members over non-members. This differs from lookup of 
named functions..."
I think you're misinterpreting that. Stroustrup is saying that ordinary 
name lookup won't stop when a member operator is found (unlike for 
functions, where ordinary name lookup will stop if the name is found as 
an in scope member name). This doesn't mean that every class in 
existence is searched for member operators whenever you use an operator, 
only the class on the lhs is searched.
for
//inside, say, A::g():
a << b;
the compiler considers candidate functions:
a.operator<<(b);
operator<<(a, b); (current namespace and namespaces associated with the 
arguments)
built in operator<<(a, b);
Contrast with:
//inside, say, A::g():
f();
Here, if f() is a member function, lookup halts immediately (though I 
don't recall if associated namespaces are checked or not).
4) with output complete, the temporary object is destroyed and its 
destructor unlocks the lock and releases the log file.
Ok.
namespace N {
    // ...
    std::ostream& operator <<(std::ostream& os, bool i)
    {
        return os.operator <<(i);
    }
    std::ostream& operator <<(std::ostream& os, const std::string& s)
    {
        return std::operator <<(os, s);
    }
}
will actually make things work which gives me a glimmer of hope. 
Now you have non-member functions, which will be found and added to the 
overload set (assuming the functions are in scope at the call site).
One thing, I believe, I have established for sure: forgetting about the 
std::ostream& operator<<(bool), which is a member-operator, and dealing 
purely with the non-member std::ostream& operator <<(std::ostream&, const 
std::string&), it is the addition of this non-member operator to the N 
namespace (the namespace in which class Log is defined) that made the 
automatic conversion from Log to std::ostream& with the help of the 
conversion operator (operator std::ostream&()) work.
ADL on the log class means that namespace N will be searched. However, 
namespace std will be searched too, since one of the arguments is 
std::string. The problem is that although operator<< for std::string is 
being found, template argument deduction fails, since the first 
parameter requires a user defined conversion, which isn't allowed when 
deducting template arguments.
  In other words, the
compiler felt its presence within the elegible scope and performed the 
conversion automatically. Where I am failing is understanding what is 
required to bring << operators defined in std into the scope automatically 
without redefining them (such as via forwarding functions). I even tried 
adding Log to std as I described (namespaces being open) but somehow it still 
did not work.
That doesn't help the problem. The reason your std::string forwarding 
function solves the problem is because it is a non-template. For 
example, adding this forwarding function to your original code works:
namespace std
{
  ostream& operator<<(ostream& os, std::string const& s)
  {
    //call template version:
    return std::operator<< <char, char_traits<char>, allocator<char> 
 >(os, s);
  }
}
Because there is a fixed set of ostream::operator<< members, you can 
easily add them all to your Log class, forwarding to the ostream 
versions (just copy and paste them out of the basic_ostream class 
definition).
Alternatives are:
*N::Log(N::ERR) << "Here goes the error message: integer = " << i
   << ", long = " << l << std::endl;
N::Log(N::ERR).get() << "Here goes the error message: integer = " << i
   << ", long = " << l << std::endl;
etc., where operator* and the function get() both return an ostream&. 
The final alternative is a macro. e.g.
#define LOCKED_LOG(level) \
  ((mutex::scoped_guard(log_mutex),N::Log(level)))
with Log just a function returning an ostream&, and the mutex locking 
handled by the separate scoped_guard object.
I did consider them all (with the only difference being the use of 
overloaded () operator, solution that differed from the sought original by an 
extra pair of round brackets: Log(INF)() << i ... - for your (*) and .get()) 
- but thank you for your suggestions. I am just a little stubborn.
Because of the way some templated operator<< functions are defined, you 
need a non-template forwarding function. Try this:
namespace N
{
   template <class T>
   inline std::ostream& operator<<(Log const& l, T const& t)
   {
     return static_cast<std::ostream&>(l) << t;
   }
   //add overloads for ios_base&(*)(ios_base&) etc.
}
Tom