12

I've encountered some code which I think should compile, but doesn't. So I'm hoping some of the local standards experts here at SO can help :-).

I basically have some code which resembles this:

#include <iostream>

template <class T = int>
class A {
public:
    class U {
    };

public:
    U f() const { return U(); }
};

// test either the work around or the code I want...
#ifndef USE_FIX
template <class T>
bool operator==(const typename A<T>::U &x, int y) {
    return true;
}
#else
typedef A<int> AI;
bool operator==(const AI::U &x, int y) {
    return true;
}
#endif

int main() {
    A<int> a;
    std::cout << (a.f() == 1) << std::endl;
}

So, to describe what is going on here. I have a class template (A) which has an internal class (U) and at least one member function which can return an instance of that internal class (f()).

Then I am attempting to create an operator== function which compares this internal type to some other type (in this case an int, but it doesn't seem to matter).

When USE_FIX is not defined I get the following error:

test.cc: In function 'int main()':
test.cc:27:25: error: no match for 'operator==' in 'a.A<T>::f [with T = int]() == 1'

Which seems odd, because I am clearly (I think) defining a templated operator== which should cover this, in fact if I just do a little of the work for the compiler (enable USE_FIX), then I no longer get an error. Unfortunately, the "fix" doesn't work generically, only for a specific instantiation of the template.

Is this supposed to work as I expected? Or is this simply not allowed?

BTW: if it matters I am using gcc 4.5.2.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • 1
    It's simply not allowed. If I knew more about your issue I could suggest an appropriate redesign. – Edward Strange Jan 13 '11 at 07:43
  • possible duplicate of [How to deduce class type from method type in C++ templates?](http://stackoverflow.com/questions/3830491/how-to-deduce-class-type-from-method-type-in-c-templates) – Matthieu M. Jan 13 '11 at 08:30
  • 4
    The problem with this issue: it's been asked loads of times, but you just cannot find any answer without using the "nondeduced" keyword. But then if you know the keyword, you probably know the answer... searching is difficult :[ – Matthieu M. Jan 13 '11 at 08:32
  • 1
    @Matthieu: but hopefully we eventually build up enough duplicates that the searches will find them! – Cascabel Jan 15 '11 at 06:11

3 Answers3

15

The problem with const typename A<T>::U &x is that U is a dependent type and the compiler cannot deduce T from the argument (this is one of the nondeduced context).

You could, for example, have two specializations of A:

class X { };
class Y { };
class Z { };

template <> class A<X> {
public: 
    typedef Z U;
};

template <> class A<Y> {
public:
    typedef Z U;
};

If you then call:

Z a;
a == 1;

what should the compiler deduce T as? X? Y?

One solution in this particular case is to declare operator== as a nontemplate friend inside of the class template:

template <class T = int>
class A {
public:
    class U {
    };

    friend bool operator==(const U& x, int y) {
        return true;
    }

public:
    U f() const { return U(); }
};
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • @James : by the way, `Z` should be declared inside the class template, only then his situation and your example would be fit together. What do you say? I mean, here `Z` is independent of `A`, whereas `U` in the question is a dependent-type. – Nawaz Jan 13 '11 at 07:59
  • 3
    @Nawaz: It doesn't matter whether `Z` is dependent; it matters that `U` is dependent. `Z` can be anything. (I had originally used `int`, but that was wrong because the function template wouldn't even be considered during overload resolution due to the built-in operator, so I introduced `Z` as a type with no nontemplate overload of `operator==`). – James McNellis Jan 13 '11 at 08:08
  • @James : what I meant was that this line `Z a;` should rather be written as `typename A::U a;` or `typename A::U a;` to pinpoint the similarity of your examples with his situation! – Nawaz Jan 13 '11 at 08:18
  • @Nawaz: While it might make it slightly simpler to follow to a casual reader, the fact is that they are 100% equivalent and the compiler does not care at all. After `typename A::U a;`, `typename A::U` and `Z` are aliases to the same exact type, which is exactly the reason why the type cannot be deduced. – David Rodríguez - dribeas Jan 13 '11 at 08:50
  • @James: I find it strange that the `friend` declaration is inside `A` and not inside `A::U`. I have tested with g++ 4.1 moving the friend definition to `A::U` and it also works, is there any difference? If you don't know off the top of your head, I can try to dig it out of the standard later. Thanks. – David Rodríguez - dribeas Jan 13 '11 at 08:56
  • @David: It could be in either scope (within `A` or within `A::U`). During overload resolution, ADL should ensure that all the enclosing scopes are searched for candidates. What I don't know is whether the search starts at the most nested possible scope and proceeds scope-by-scope outwards until a match is found and then stops or whether the scopes are all searched and then the best match is selected from all of the potential matches. (Overload resolution is way too freakin' complicated.) – James McNellis Jan 13 '11 at 09:03
  • 1
    @David : I understand that `typename A::U a;` and `Z a;` are exactly same, and that is the reason, as you said, why the type `T` in `operator==` cannot be deduced. But these two syntax makes difference to the programmer (not to the compiler), as the synax `typename A::U a;` makes programmer believe that the compiler can deduce the type `T` from `a` for `operator==`. I believe that is why the topic starter thought it could be a possible bug in the compiler itself, not in his code!! That is all I wanted to say. :-) – Nawaz Jan 13 '11 at 09:07
  • @James: Well, that part I know. Once a candidate is found in one scope, the search does not continue outwards. `struct base { void f(int); }; struct derived { void f(double); void g() { f(1); } };` in that snippet, the call inside `g()` will find `derived::f(double)` and will call it without trying the next scope (base class) where there is a better candidate `base::f(int)`. Another example is implementing a swap method: `using namespace std; struct test { int x; void swap( test& other ) { swap( x, other.x ); } }; This will fail, as `swap` is found in `test` with the wrong type of arguments... – David Rodríguez - dribeas Jan 13 '11 at 09:33
  • ... without going out to find `std::swap` (namespace level, accessible through `using namespace std;`) In this case the solution is either qualifying or adding a `using namespace` directive inside the method itself: `struct test { int x; void swap( test& other ) { using namespace std; swap( x, other.x ); } };` – David Rodríguez - dribeas Jan 13 '11 at 09:33
  • @David: Oh right. I guess what I really meant is that I knew that but wasn't thinking... :| Thank you. – James McNellis Jan 13 '11 at 12:16
11
template <class T>
bool operator==(const typename A<T>::U &x, int y) {
    return true;
}

Using this template, it is not permissible (or sometimes possible) to deduce the template parameter T from the type of x. It is what is known as a non-deducible context. (E.g. Somebody could specialize A for a different parameter, say double and make A<double>::U a typedef for A<int>::U.)

There is no workaround, you would have to explicitly specify the template parameter which for operator== makes for ugly syntax.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
4

It is not allowed for rather obvious reasons. In general case there's really no way the compiler can deduce the template argument from your call to operator ==. Apparently you assumed that the nested type U uniquely defines the enclosing A specialization. That is not true, which can be illustrated by the following example with two explicit specializations of A

template <> class A<int> {
public:
  class U {};
};

template <> class A<double> {
public:
  typedef A<int>::U U;
};

In this case, if you call templated operator == with an argument of type A<int>::U the compiler cannot deduce template argument T for templated operator ==. Should T be int or double? There's no way to say.

In order to avoid these ambiguities such situations are called non-deduced contexts. Deducing the enclosing class template arguments from a nested type is an example of non-deduced context.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765