11

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?

Columbo
  • 60,038
  • 8
  • 155
  • 203
Sebastian Kramer
  • 689
  • 1
  • 9
  • 18
  • I tried your code with MSVC13 and it compiles fine, uses the int version in the first case and the MyClass version with the second. You should add infor about compiler you are using – Christophe Nov 08 '14 at 20:50
  • I use clang --version clang version 3.5.0 (tags/RELEASE_350/final). – Sebastian Kramer Nov 08 '14 at 20:56
  • [Another question with the same principle](http://stackoverflow.com/questions/8501294/different-behavior-for-qualified-and-unqualified-name-lookup-for-template/8501421#8501421) – M.M Nov 10 '14 at 02:30

2 Answers2

8

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

Community
  • 1
  • 1
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • @SebastianKramer This is the so-called "two phase lookup". Consider `void foo(void *); template void bar(T /*dummy*/){ foo(0); } void foo(int); int main(){ bar(0); }` Presumably the author of the template intended `bar` to call the `void *` overload rather than the `int` overload. – T.C. Nov 08 '14 at 21:10
  • @T.C. Are you sure that the fact that it works with the `using` declaration is standard compliant? I'm a little bit concerned about [namespace.udecl]/11 – Columbo Nov 08 '14 at 21:20
  • @SebastianKramer The justification of the first bullet: This is ensuring that names that do not depend on a template parameter are not searched for after the template definition. Because name lookup for non-dependent names shall be just as in usual functions. Also consider [temp.nondep]: "Non-dependent names used in a template definition are found using the usual name lookup and bound at the point they are used." – Columbo Nov 08 '14 at 21:27
  • @Columbo But you are not depending on that `using` to bring in the overload taking a `MyClass &`. You are depending on ADL. – T.C. Nov 08 '14 at 21:35
  • Also if you rename the virtual method to like draw_impl, an unqualified call to draw just works. At least on gcc. – Barry Nov 08 '14 at 21:40
  • I don't see how this is anything to do with ADL; instead it's about when names inside template functions are looked up. – M.M Nov 10 '14 at 00:34
  • @MattMcNabb Did you read the first quote? Ordinary lookup is done in the definition context and ADL with the instantiation context. "Deferred name lookup" is the unofficial way of explaining this. – Columbo Nov 10 '14 at 00:56
  • resolved the C++14 issue - that changed qualified-IDs to no longer be considered dependent names. – M.M Nov 10 '14 at 02:41
  • I didn't realize that "ADL" meant everything in 3.4.2, had just been thinking of it as searching extra namespaces based on the types of the parameters. In this example no extra namespaces are searched; but the existing ones get searched again at the point of instantiation. – M.M Nov 10 '14 at 02:42
3

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

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Extending the search range helps, thanks. I'm however looking for the rational behind this behaviour. – Sebastian Kramer Nov 08 '14 at 21:01
  • @SebastianKramer well, ADL can't apply in the definition context because we don't know what the arguments or the candidate functions are there. And it is desirable that ADL does apply in the instantiation context (for the same reasons that [ADL is desirable outside of templates](http://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka-adl-or-koenig-lookup)). That leaves us with the status quo. – M.M Nov 10 '14 at 03:00
  • Although I don't yet see why the 3.4.1 lookups should not be applied in the instantiation context for a dependent function call. – M.M Nov 10 '14 at 03:02