wtorek, 14 maja 2013

Surprising "dark corner" in C++

UPDATE 21.07.2013: Surprising dark corner turned on to be a compiler bug. Anyway, I think it is worth to know how name lookup works in detail.

Today was supposed to be a normal developer day. We have found something extremely surprising regarding C++, though. At least for us. Maybe someone will eventually provide reliable explanation on SO as my Google-fu (actually, more duckduckgo-fu recently :-) ) seems to have worsened.

This is a MWE of what I'm talking about:
 1 template <typename T> class Widget { };
 2 
 3 class Base
 4 {
 5 public:
 6     virtual void handle(Widget<float> widget) { }
 7     virtual void handle(Widget<int> widget) { }
 8 };
 9 
10 class Derived : public Base
11 {
12 public:
13     virtual void handle(Widget<int> widget) { }
14 };
15 
16 int main(int, char **)
17 {
18     Derived * derived = new Derived();
19     derived->handle(Widget<float>());
20     delete derived;
21 }
The code is not complicated. It is even not complex. We have some empty Widgettemplate class that is used as an argument. Below two classes are defined - Base andDerived. All methods in base class are virtual. Derived class does not provide implementation for a method that accepts Widget<float> as an argument. In main function we simply try to invoke this method.

We expect that Base::handle(Widget<float> const& widget) is going to be called. However before we run this simple program, we got compilation error.
$ clang++ polym.cpp
polym.cpp:20:21: error: no viable conversion from 'Widget<float>' to 'Widget<int>'
    derived->handle(Widget<float>());
                    ^~~~~~~~~~~~~~~
polym.cpp:1:29: note: candidate constructor (the implicit copy constructor)
                      not viable: no known conversion from 'Widget<float>' to
      'const Widget<int> &' for 1st argument;
template  class Widget { };
                            ^
polym.cpp:13:37: note: passing argument to parameter 'widget' here
    virtual void handle(Widget<int> widget) { }
                                    ^
1 error generated.
Wait, what has just happened? Did compiler just tried to perform implicit conversion fromWidget<float> to Widget<int>? Why? I'm afraid I can't give you precise answer. This was tested on gcc-4.7.2 and clang-3.0.

I suspect that compiler finds other methods with similar signatures and tries to match template parameter. Other lookup branches are dropped, so signature is not found in base class too. After it turns out that there is no exact match, implicit conversions are employed. Another option is that in such situations methods are shadowed, but that would be against polymorphism. Those are only my suspictions, do you have other ideas?

The question is open. So far I'm aware of two solutions.
16 int main(int, char **)
17 {
18     Derived * derived = new Derived();
19     dynamic_cast<Base*>(derived)->handle(Widget<float>());
20     delete derived;
21 }
10 class Derived : public Base
11 {
12 public:
13     virtual void handle(Widget<int> widget) { }
14     using Base::handle;
15 };
The first solution is rather straightforward. The other one is pretty curious, though.