Function specialization resolution in namespaces

Started by
9 comments, last by a light breeze 5 years, 5 months ago

I'm guessing the problem might be tied to my overall design, but I'd like to know why this behavior happens. Some background:

I have a logger class, which implements a number of << operators for common types. 


namespace logging {
	class log {
		public:
...
			_FRIEND _INLINE
			logging::log& operator << (IN logging::log& l, IN void* v) {
				LOG_APPEND_SPRINTF_MSG(32, "%p", v); return l;
			}
...
		};
}
		

There's a reason I singled out the overload for void*. My vector class provides an automatic cast operator to float*. In order to output a vector, however, I provide a special case for the << operator in global namespace:


_INLINE
logging::log& operator<<(logging::log& log, const math::vec3& v)
{
    log << '(';
    log.append_float(v.x);
    log << ", ";
    log.append_float(v.y);
    log << ", ";
    log.append_float(v.z);
    log << ')';

    logging::dispatch_log_message(log);

    return log;
}

Now, if I log a vec3 in a function that lies in the global namespace, the proper operator for << gets called. However, when logging from a function that resides inside any namespace, Visual Studio first casts the vector to float* and then forwards that to the void* operator.

For now I only know one bad solution apart from rewriting my logging class - define the << operator for vec3 in the respective namespace (this has to be done for all namespaces that want to log a vector type).


namespace mynamespace {
	logging::log& operator<<(logging::log& log, const math::vec3& v) ...
}

void mynamespace:foo(math::vec3& v){
	log << v << endl;
}

This extremely annoying. What prompts this behavior/operator resolution? More importantly, can this be solved without expelling the base type operators from the logging class (I mean, frankly I haven't even tried, but for now I'm also using certain internal functions so I would need to restructure the entire logging class to boot; besides - I'd like to know if there's a simpler solution out of sheer principle :) )?

Advertisement

I'm not sure how it even finds the void * version.  Is there a "using namespace logging" somewhere?

I can't stand namespaces myself.  Never use them unless I'm forced to. Somehow they seem to cause more problems than they fix.
 

4 hours ago, Gnollrunner said:

I'm not sure how it even finds the void * version.  Is there a "using namespace logging" somewhere?

I can't stand namespaces myself.  Never use them unless I'm forced to. Somehow they seem to cause more problems than they fix.
 

The void* operator is a friend member of the logging class - it doesn't need exposure to the global namespace.

I personally love structuring my code with namespaces. It's a way to filter everything.

1 hour ago, irreversible said:

The void* operator is a friend member of the logging class - it doesn't need exposure to the global namespace.

Oh I get it _FRIEND is friend and that makes it a non-member.  Well I have no clue. I usually start changing things until I zero in on the problem. C++ has gotten so complex it could very well be a compiler bug. I just update my visual studio and got a raft or error messages I didn't get earlier in the day.

Doesn't your compiler give you a warning?

Your code snipets are not enough to be able to see what is happening.

 

Nope - no warning or anything. The wrong operator just is called blindly. As for it being a compiler bug - it's possible, although it would be nice if I could get some external verification for this since I only have VS set up on my rig. I can't recall if this used to be the case in VS2013 as well or not...

As for the code - there isn't much else to show really. This is kinda the minimal example. I might write up a compileable test program when I get back to my computer.

I highly doubt it being the compiler's fault but if you do a test program I would gladly take a look at it.

Just put your overload into the logging namespace.

As a general rule, you should never put operator overloads in the global namespace.  Always but operator overloads in the same namespace as (one of) the type(s) on which they operate.  In this case, that's class logging::log in the logging namespace.

1 hour ago, a light breeze said:

Just put your overload into the logging namespace.

As a general rule, you should never put operator overloads in the global namespace.  Always but operator overloads in the same namespace as (one of) the type(s) on which they operate.  In this case, that's class logging::log in the logging namespace.

Hey - this works!

Like I mentioned in my original post, though - I'd like to understand why this is the case. It would seem the operator defined in the same (in this case global) namespace should, regardless of placement, be preferred by a call from the same namespace.

PS - I'm also not quite sure why not to put these specific overloads in global since they get exposed via an early using namespace logging; anyway.

Short version: functions found through argument-dependent lookup usually have priority over names found through other methods, so the friend operator is shadowing (i.e. hiding) the operator in the global namespace.  Putting the overload in the logging namespace allows it to be found with argument-dependent lookup, which prevents it from being shadowed.  See also: unqualified lookup rules.

This topic is closed to new replies.

Advertisement