Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 522
Sebastian Kramer Avatar asked Nov 08 '14 20:11

Sebastian Kramer


1 Answers

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

like image 144
Columbo Avatar answered Sep 24 '22 21:09

Columbo