Sebastian Kramer
Sebastian Kramer

Reputation: 719

Why does the compiler choose the incorrect function overload in this case?

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?

Upvotes: 11

Views: 882

Answers (2)

Columbo
Columbo

Reputation: 60979

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 let Y be the lookup set produced by argument dependent lookup (defined as follows). If X 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. Otherwise Y 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&.

Upvotes: 8

Barry
Barry

Reputation: 302748

When you directly call ::draw(), you're not able to use ADL correctly. (Why? I don't actually know specifically and hopefully somebody will come in and explain this to me too [edit: see Columbo's answer with the why]) But in order to actually use ADL, you need to make an unqualified call to draw like so:

void draw(std::ostream& out) const
{
    using ::draw;
    draw(data, out);
}

That will correctly find the overload draw(const MyClass&, std::ostream&).

Upvotes: 3

Related Questions