Я думал об использовании классов CRTP, чтобы помочь с перегрузкой, и подумал, что будет делать следующий фрагмент кода:
#include <iostream>
#include <typeinfo>
template <class TDerived>
class FunctionImplementation
{
};
class AbstractFunction
{
};
class BaseFunction : public AbstractFunction, public FunctionImplementation<BaseFunction>
{
};
class DerivedFunction : public BaseFunction, public FunctionImplementation<DerivedFunction>
{
};
template <class TDerived>
void foo(const FunctionImplementation<TDerived>& function) {
std::cout << "In foo for " << typeid(TDerived).name() << std::endl;
}
int main() {
BaseFunction base;
DerivedFunction derived;
foo(base);
foo(derived);
}
С GCC 4.2 на OS X он не компилируется:
overload.cpp: In function ‘int main()’:
overload.cpp:31: error: no matching function for call to ‘foo(DerivedFunction&)’
С Clang 4.0 в той же системе он компилируется и выполняет «естественную» вещь при запуске:
In foo for 12BaseFunction
In foo for 15DerivedFunction
В Visual C ++ 2010 он также компилируется, но работает по-другому:
In foo for class BaseFunction
In foo for class BaseFunction
Наконец, GCC 4.7.2 в Linux не компилируется, но выдает более полное и достаточно авторитетное сообщение об ошибке:
overload.cpp: In function ‘int main()’:
overload.cpp:31:16: error: no matching function for call to ‘foo(DerivedFunction&)’
overload.cpp:31:16: note: candidate is:
overload.cpp:22:6: note: template<class TDerived> void foo(const FunctionImplementation<TDerived>&)
overload.cpp:22:6: note: template argument deduction/substitution failed:
overload.cpp:31:16: note: ‘DerivedFunction’ is an ambiguous base class of ‘const FunctionImplementation<TDerived>’
Что правильно? Я не эксперт в навигации по стандарту языка …
В этом случае я считаю, что gcc прав. Вы просите компилятор выполнить вывод типа для вас, и проблема в том, что данный аргумент типа DerivedFunction
это не FunctionImplementation<TDerived>
напрямую, поэтому необходимо выполнить преобразование. На данный момент список конверсий включает в себя FunctionImplementation<BaseFunction>
(через BaseFunction
) а также FunctionImplementation<DerivedFunction>
(Непосредственно). Там нет порядка среди этих двух вариантов, поэтому компилятор выручает с неоднозначностью.
Стандарт рассматривает это в §14.8.2.1 [temp.deduct.call] / 4,5
(параграф 4) В общем, процесс вывода пытается найти значения аргументов шаблона, которые сделают выведенный A идентичным A (после преобразования типа A, как описано выше). Однако есть три случая, которые допускают разницу:
[…]Если P является классом и P имеет форму simple-template-id, то преобразованный A может быть производным классом выведенного A. Аналогично, если P является указателем на класс вида simple-template-id, преобразованный A может быть указателем на производный класс, на который указывает выведенный A.
(пункт 5) Эти альтернативы рассматриваются только в том случае, если в противном случае вычет типа не удался. Если они дают более одного возможного вывода A, вывод типа не выполняется.
В четвертом абзаце это позволяет выводу типа выбрать базу типа аргумента, в этом случае есть 2 таких основания. Пятый абзац определяет, что если применение предыдущего правила дает более одного результата, то вывод типа не выполняется.
Хорошо, ответ ниже неправильный. Я всегда верил из чтения 13.3.1p7
В каждом случае, когда кандидат является шаблоном функции, кандидаты-специализации шаблона функции генерируются с использованием вывода аргумента шаблона (14.8.3, 14.8.2). Затем эти кандидаты обрабатываются как функции-кандидаты обычным способом.
в этом методе выведения аргументов использовался соответствующий механизм разрешения перегрузки для выбора среди синтаксически возможных специализаций (разрешение перегрузки функций для функций и т. д.).
Оказывается, это не так: вывод аргументов шаблона имеет свой собственный, очень ограниченный набор правил, которые настаивают на точных совпадениях (cv-квалификаторы и разыменование и т. П.), Разрешая только преобразование аргумента производного класса в шаблонный базовый рассматривается здесь как особый случай — и этот особый случай явно запрещает использование разрешения перегрузки функции для обработки любых неоднозначностей.
Так что для правильного ответа смотрите выше. Я оставляю этот ответ здесь, потому что он получает отклик, заставляя меня поверить, что я не единственный, кто ошибается таким образом:
Разрешение перегрузки для foo(derived)
смотрит вверх FunctionImplementation<T>
объявления в классе Derived
, Учебный класс Derived
не имеет объявлений области видимости этого шаблона, поэтому результаты рекурсивного поиска из его базовых классов объединяются, что приводит к обеим специализациям в его иерархии:
Derived
: Base
: AbstractFunction
, FunctionImplementation<Base>
, FunctionImplementation<Derived>
Учитывая глубину, на которой было найдено объявление в иерархии деривации базового класса, при поиске имени будет означать, что ни одно имя или основание никогда не могут быть добавлены в класс без молча влияет на предыдущие результаты во всех производных классах, которые используют множественное наследование. Вместо этого C ++ отказывается выбирать для вас и предоставляет декларации using для явного объявления, какой базовый класс использует имя (в данном случае tepmlate), к которому вы обращаетесь.
Стандарт для этого в 10.2, p3-5 — мясо.