#c #c 11
#c #c 11
Вопрос:
Подготовьте следующий код:
template <class Impl, class Cont>
struct CrtpBase
{
void foo()
{
cout << "Default foon";
Cont cont;
cont.push_back(10); // Going to fail if Cont::push_back doesn't exist
}
};
typedef std::unordered_map<int,int> ContType;
struct Child : public CrtpBase<Child, ContType>
{
typedef CrtpBase<Child, ContType> _Parent;
// using _Parent::foo // (1)
void foo() { cout << "Childn"; }
};
int main()
{
Child obj;
obj.foo(); // (2)
return 0;
}
В чем я застрял, так это в условиях, когда создается экземпляр класса CrtpBase, а когда нет.
На мой взгляд, в точке (2), когда я вызываю foo()
, компилятор должен сгенерировать список возможных перегрузок. Это должны быть Child::foo()
и Child::_Parent::foo()
. Поэтому Child::_Parent::foo()
должен быть создан экземпляр. (На этом этапе компиляция должна завершиться неудачей, поскольку ошибка находится в основной функции, SFINAE неприменимо) Затем компилятор должен выбрать соответствие приоритета.
Однако программа компилируется и показывает, что CrtpBase::foo
экземпляр не создан. Вопрос в том, почему. Компилятор каким-то образом знает, что Child::foo
это будет наилучшее соответствие, и останавливает процесс разрешения перегрузки.
Когда я раскомментирую // using ...
, явно запрашивая компилятор использовать перегрузку базовой функции в качестве одного из совпадений, ничего не меняется, он снова компилируется
Просто чтобы сделать какой-то вывод из того, что я понял из приведенных ниже ответов, поскольку они охватывают различные аспекты происходящего
-
Компилятор даже не учитывает базовую перегрузку. Это важный факт, которого я не видел, отсюда и все недоразумения
-
Здесь это не так, но выбор наилучшего соответствия во время разрешения перегрузки создает экземпляр только выбранного соответствия
Комментарии:
1. @GillBates Тогда почему раскомментирование
using
не вызывает ошибок?2. Это не меняет того факта, что вы все еще скрываете
foo()
базовый класс. Их подписи одинаковы.3. @GillBates я не понимаю. Как факт скрытия отключает создание экземпляра? Чтобы выяснить, совпадают ли сигнатуры, компилятор должен увидеть, что в базовом классе есть такая функция. Следовательно, он должен создать его экземпляр, нет?
4. Нет, компилятор может проверять сигнатуры функций без создания экземпляров шаблонов здесь. На самом деле это не то, что здесь происходит, см. Ответ @Barry’s .
Ответ №1:
На мой взгляд, в точке (2), когда я вызываю foo(), компилятор должен сгенерировать список возможных перегрузок.
Верно. Мы начнем с поиска имени foo
в Child
. Мы находим Child::foo
, а затем останавливаемся. У нас есть только один кандидат, который является жизнеспособным кандидатом, поэтому мы называем его. Мы не продолжаем искать в базовых классах, поэтому CrtpBase::foo
это никогда не рассматривается. Это верно независимо от сигнатур (если CrtpBase::foo()
бы принял an int
, obj.foo(4)
не удалось бы скомпилировать, потому Child::foo()
что не принимает аргумент — даже если гипотетический вызов был CrtpBase::foo(4)
бы правильно сформирован).
Когда я раскомментирую
// using ...
, явно запрашивая компилятор использовать перегрузку базовой функции в качестве одного из совпадений, ничего не меняется, он снова компилируется
На самом деле это не то, что вы делаете. Using-declaration вводит имя CrtpBase::foo
в область видимости Child
, как если бы оно было объявлено там. Но тогда объявление void foo()
скрывает эту перегрузку (в противном случае вызов был бы неоднозначным). Итак, снова поиск имени для foo
in Child
находит Child::foo
и останавливается. Где этот случай отличается от предыдущего, так это если CrtpBase::foo()
принимать разные аргументы, тогда он будет рассмотрен (например, мой гипотетический obj.foo(4)
вызов в предыдущем абзаце).
Комментарии:
1. Кстати, я пытался это сделать, прежде чем публиковать вопрос. Если я переключусь
CtrpBase::foo()
наCtrpBase::foo(int)
и вызовуobj.foo(1)
внутриmain
, я получу ‘main.cpp:85:14: error: ‘класс std::unordered_map<int, int>’ не имеет члена с именем ‘push_back’, поэтому компилятор идет еще глубже?2. да:) , я сослался на первую часть вашего ответа. Вы сказали, что компиляция завершится неудачей, даже если
CrtpBase::foo(4)
она правильно сформирована. Я утверждаю, что это неправильно. Извините, если вас неправильно поняли3. @DimG В первом абзаце предполагается, что нет
using _Parent::foo;
4. Ха, моя вина! Я думал, что прокомментировал это обратно. Спасибо
Ответ №2:
Экземпляры шаблонных классов могут быть созданы без создания экземпляров их методов.
Child
не является экземпляром шаблонного класса. Его методы всегда создаются.
Простое разрешение перегрузки имени метода экземпляра класса шаблона не запускает создание экземпляра метода класса шаблона. Если он не выбран, он не создается.
Эта функция изначально была добавлена в предыстории C . Это позволяло std::vector<T>
иметь an operator<
, который условно работал бы в зависимости от того, был ли T
an operator<
в эпоху C до SFINAE.
Для более простого случая:
template<class T>
struct hello {
void foo() { T t{}; t -= 2; }
void foo(std::string c) { T t{}; t = c; }
};
Вызов hello<int>.foo()
рассматривает hello::foo(std::string)
и не создает ее экземпляр.
Вызов hello<std::string>.foo("world");
рассматривает hello::foo()
и не создает ее экземпляр.
Комментарии:
1. спасибо, это было действительно полезно, я этого не знал! Однако, похоже, что базовая перегрузка даже не рассматривается