шаблоны — Должен ли этот бит C ++ CRTP-кода компилироваться, и если да, что он должен делать?

Я думал об использовании классов 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>’

Что правильно? Я не эксперт в навигации по стандарту языка …

1

Решение

В этом случае я считаю, что 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 таких основания. Пятый абзац определяет, что если применение предыдущего правила дает более одного результата, то вывод типа не выполняется.

3

Другие решения

Хорошо, ответ ниже неправильный. Я всегда верил из чтения 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 — мясо.

1

По вопросам рекламы [email protected]