I'm trying out the code presented by Sean Parent at his talk at GoingNative 2013 - "Inheritance is the base class of evil". (code from the last slide available at https://gist.github.com/berkus/7041546
I've tried to achieve the same goal on my own but I can't understand why the below code won't act as I expect it to.
#include <boost/smart_ptr.hpp>
#include <iostream>
#include <ostream>
template <typename T>
void draw(const T& t, std::ostream& out)
{
std::cout << "Template version" << '\n';
out << t << '\n';
}
class object_t
{
public:
template <typename T>
explicit object_t (T rhs) : self(new model<T>(rhs)) {};
friend void draw(const object_t& obj, std::ostream& out)
{
obj.self->draw(out);
}
private:
struct concept_t
{
virtual ~concept_t() {};
virtual void draw(std::ostream&) const = 0;
};
template <typename T>
struct model : concept_t
{
model(T rhs) : data(rhs) {};
void draw(std::ostream& out) const
{
::draw(data, out);
}
T data;
};
boost::scoped_ptr<concept_t> self;
};
class MyClass {};
void draw(const MyClass&, std::ostream& out)
{
std::cout << "MyClass version" << '\n';
out << "MyClass" << '\n';
}
int main()
{
object_t first(1);
draw(first, std::cout);
const object_t second((MyClass()));
draw(second, std::cout);
return 0;
}
This version handles printing int
fine, but fails to compile in the second case as the compiler doesn't know how to use MyClass
with operator<<
. I can't understand why the compiler won't choose the second overload provided specifically for the MyClass
. The code compiles and works fine if I change the model::draw() method's name and remove the ::
global namespace specifier from its body, or if I change the MyClass' draw global function to a complete template specialization.
The error message I get is as below, after that is a bunch of candidate function not viable...
t76_stack_friend_fcn_visibility.cpp:9:9: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const MyClass')
out << t << '\n';
~~~ ^ ~
t76_stack_friend_fcn_visibility.cpp:36:15: note: in instantiation of function template specialization 'draw<MyClass>' requested here
::draw(data, out);
^
t76_stack_friend_fcn_visibility.cpp:33:9: note: in instantiation of member function 'object_t::model<MyClass>::draw' requested here
model(T rhs) : data(rhs) {};
^
t76_stack_friend_fcn_visibility.cpp:16:42: note: in instantiation of member function 'object_t::model<MyClass>::model' requested here
explicit object_t (T rhs) : self(new model<T>(rhs)) {};
^
t76_stack_friend_fcn_visibility.cpp:58:20: note: in instantiation of function template specialization 'object_t::object_t<MyClass>' requested here
const object_t second((MyClass()));
^
Why is the template version of global draw template function choosen over the MyClass function overload? Is it because the template reference is greedy? How to fix this issue?
Because you use a qualified name in the function call. [temp.dep.candidate]:
For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:
- For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.
- For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.
§3.4.2 (alias [basic.lookup.argdep]):
When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function declarations (11.3) not otherwise visible may be found.
So essentially ADL doesn't apply since the call uses a qualified-id.
As Barry shows in his answer you can resolve this by making the call unqualified:
void draw(std::ostream& out) const
{
using ::draw;
draw(data, out);
}
You have to add a using
-declaration before that though. Otherwise unqualified name lookup will find the model<>::draw
member function first when searching the declarative regions in ascending order, and will not search any further. But not only that - because model<>::draw
(which is a class member) is found my unqualified name lookup, ADL is not invoked, [basic.lookup.argdep]/3:
Let
X
be the lookup set produced by unqualified lookup (3.4.1) and letY
be the lookup set produced by argument dependent lookup (defined as follows). IfX
contains
- a declaration of a class member, or
- a block-scope function declaration that is not a using-declaration, or
- a declaration that is neither a function or a function template
then
Y
is empty. OtherwiseY
is the set of declarations found in the namespaces associated with the argument types as described below.
Hence, if the using
-declaration is provided the only declaration found by unqualified name lookup will be the global draw
template that was introduced into the declarative region of model::draw
.
ADL is then invoked and finds the later declared draw
function for MyClass const&
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With