tags:

views:

168

answers:

3

Heya, folks. I'm porting some code from a project largely developed in MSVS to use g++. I've found a lot of little differences, mostly things that MSVS allows but g++ does not. Usually it's something involving c++ standards, things that MSVS lets slide, but I'm having trouble seeing just what's wrong with one particular section.

g++ is having trouble matching a call to operator !=, but only in a specific context. Looking up operator != for a particular nested class works if the hosting class is not a template. If I turn the hosting class into a class template, however, everything breaks. I'm either missing something fundamental to c++, or g++ is doing something wrong.

I've learned not to cry "Compiler Bug!" too often, so I wanted to see if anyone here can see what I'm missing.

This worked example shows the working, non-template version, and then the broken, template version. g++ --version gives: g++ (Ubuntu 4.4.1-4ubuntu9) 4.4.1

Working reference version without templates

namespace Works {

struct host {
    struct iterator {};
    iterator op();
};

bool operator != (host::iterator const& a0, host::iterator const& a1);

bool f() {
    return host().op() != host().op();
}

} // namespace Works

Broken version with templates

namespace Broken {

template <typename T>
struct host {
    struct iterator {};
    iterator op();
};

template <typename T>
bool operator != (typename host<T>::iterator const& a0, 
                   typename host<T>::iterator const& a1);

bool f() {
    return host<int>().op() != host<int>().op();
}

} // namespace Broken

The template version fails with the errors:

Main.cpp: In function ‘bool Broken::f()’:
Main.cpp:50: error: no match for ‘operator!=’ in ‘Broken::host<int>().Broken::host<T>::op [with T = int]() != Broken::host<int>().Broken::host<T>::op [with T = int]()’
A: 

i hit a similar thing. C++ operator overloads are supposed to take const inputs. msvc will let you get away with using non const inputs. G++ insists they are const

pm100
+5  A: 

This works neither in msvc nor gcc.

The problem is that in host<T>::iterator, T is in non-deducible context. Since neither parameter allows T to be deduced, the function template cannot be instantiated.

That's why you usually define overloaded operators inside the class.

struct iterator
{
    friend bool operator != (iterator const & lhs, iterator const & rhs)
    {
        return false;
    }
};
avakar
How would you define that `operator !=` later?
Omnifarious
@Omnifarious, well, I'm not sure. I even opened up the standard for this, but I haven't found and answer. Right now, I'm not sure it's even possible. You can define the function inline, of course.
avakar
I believe the syntax is: `bool operator!=(/*...*/) {return false;}` in the global namespace without any scope resolution operators. The declaration does not indicate any namespace nor class name sponsoring the function.
Thomas Matthews
@Thomas Matthews, you have to remember that `iterator` is a member class inside `host` class template. A unique `operator!=` non-template function is declared for each specialization of `host` that gets instantiated. In fact, I am now pretty sure that you can't define the function outside the class.
avakar
@avakar, So, perhaps defining it inline as `return Broken::operator !=<int>(lhs, rhs);` would work. :-)
Omnifarious
@avakar, it does work, and I wrote an answer including the working code. This is a really interesting case.
Omnifarious
A: 

Building an @avakar's answer, I got the code to work properly, though it's a little strange:

namespace Broken {

template <typename T> struct host;

template <typename T>
bool operator != (typename host<T>::iterator const& a0,
                  typename host<T>::iterator const& a1);

template <typename T>
struct host {
    struct iterator {
       friend bool operator !=(iterator const & lhs, iterator const & rhs) {
          return operator !=<int>(lhs, rhs);
       }
    };
    iterator op();
};

bool f() {
    // The following does indeed end up calling the operator != you want it to call.  I
    // have a slightly different version in which I tested this.
    return host<int>().op() != host<int>().op();
}

} // namespace Broken

This gets around the problem that the template arguments to the top level operator != cannot be deduced by calling it with an explicit template argument from a friend function. It requires forward declaring the host template.

Omnifarious
Well, but you are defining the function inline. And if you *can* define it inline, then there is little reason to define it outside the class as well except to limit coupling. And if you wanted to do that, you probably wouldn't call the free function template `operator!=`, but you'd give it a more meaningful name, and you'd put it in namespace `detail`.
avakar