Потенциальная ошибка шаблона g++?
Я столкнулся с некоторым кодом, который я думаю должен компилироваться, но не делает этого. Поэтому я надеюсь, что некоторые из экспертов по местным стандартам здесь, в SO, могут помочь: -).
У меня в основном есть код, который напоминает это:
#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;
}
Итак, чтобы описать, что здесь происходит. У меня есть шаблон класса (A
), который имеет внутренний класс (U
) и по крайней мере одну функцию-член, которая может возвращать экземпляр этого внутреннего класса (f()
).
Затем я пытаюсь создать функцию operator==
, которая сравнивает этот внутренний тип с каким-либо другим типом (в данном случае a int
, но это, похоже, не имеет значения).
Если USE_FIX
определено не, я получаю следующую ошибку:
test.cc: In function 'int main()':
test.cc:27:25: error: no match for 'operator==' in 'a.A<T>::f [with T = int]() == 1'
Что кажется странным, потому что я ясно (я думаю), определяя шаблонный operator==
, который должен охватывать это, на самом деле, если я просто немного поработаю над компилятором (включите USE_FIX), то я больше не получаю ошибка. К сожалению, "исправление" не работает в общем случае, только для конкретного экземпляра шаблона.
Должен ли он работать так, как я ожидал? Или это просто не разрешено?
Кстати: если это важно, я использую gcc 4.5.2.
Ответы
Ответ 1
Проблема с const typename A<T>::U &x
заключается в том, что U
является зависимым типом, и компилятор не может вывести T
из аргумента (это один из недооцененных контекстов).
Вы могли бы, например, иметь две специализации A
:
class X { };
class Y { };
class Z { };
template <> class A<X> {
public:
typedef Z U;
};
template <> class A<Y> {
public:
typedef Z U;
};
Если вы звоните:
Z a;
a == 1;
что должен сделать компилятор T
как? X
? Y
?
Одним из решений в этом конкретном случае является объявление operator==
как друга без шаблона внутри шаблона класса:
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(); }
};
Ответ 2
template <class T>
bool operator==(const typename A<T>::U &x, int y) {
return true;
}
Используя этот шаблон, недопустимо (или иногда возможно) выводить параметр шаблона T
из типа x
. Это то, что известно как не выводимый контекст. (Например, кто-то может специализировать A
для другого параметра, например double
и сделать A<double>::U
typedef для A<int>::U
.)
Нет обходного пути, вам нужно явно указать параметр шаблона, который для operator==
делает для уродливого синтаксиса.
Ответ 3
Это не допускается по вполне очевидным причинам. В общем случае не существует способа, с помощью которого компилятор может вывести аргумент шаблона из вашего вызова оператору ==
. По-видимому, вы предположили, что вложенный тип U
однозначно определяет прилагаемую A
специализацию. Это неверно, что можно проиллюстрировать следующим примером с двумя явными специализациями A
template <> class A<int> {
public:
class U {};
};
template <> class A<double> {
public:
typedef A<int>::U U;
};
В этом случае, если вы вызываете templated operator ==
с аргументом типа A<int>::U
, компилятор не может вывести аргумент шаблона T
для шаблонного оператора ==
. Должно ли T
быть int
или double
? Невозможно сказать.
Чтобы избежать этих двусмысленностей, такие ситуации называются не выводимыми контекстами. Выделение аргументов шаблона вмещающего класса из вложенного типа является примером не выводимого контекста.