Взаимодействие между шаблонами и перегрузками

Вывод следующего кода — TA, но я не понимаю, почему.

#include <iostream>
#include <type_traits>

struct A {};

template<typename T>
void fun(T) {
    std::cout << "T";
}

template<typename T>
void caller(T t) {
    fun(A{});
    fun(t);
}

void fun(A) {
    std::cout << "A";
}

int main() {
    caller(A{});
}

Мое понимание шаблонов говорит мне следующее:

  • непосредственно перед разбором тела main единственная функция «на месте» — это нешаблонная перегрузка fun, та, что печатает A, потому что шаблоны fun и caller не использовались и, в свою очередь, не создавались экземпляры;
  • только когда вызов caller анализируется, шаблон caller создается с помощью T = A, что подразумевает, что caller вызывает fun с объектами одного типа в обоих случаях;
  • кроме того, поскольку этот тип — A, я бы сказал, что в обоих случаях вызывается нешаблонная перегрузка fun.

Однако вышесказанное неверно, иначе я бы получил AA (на самом деле, тоже TT меня меньше удивил бы, чем TA).

Я также заметил, что при перемещении нешаблонной перегрузки перед определением caller вывод становится AA, поэтому я могу только предположить:

  • когда синтаксический анализатор читает строку fun(A{});, шаблон fun создается с помощью T = A, даже если шаблон caller еще не создается;
  • затем, когда caller(A{}); анализируется, создается экземпляр caller;
  • только в этот момент второй вызов fun в теле caller может быть интерпретирован как вызов с аргументом типа A, но на этот раз нешаблон fun уже известен компилятору, поэтому он выбран как лучшее соответствие.

Я не знаю, имеет ли смысл вышеизложенное.

Более предсказуемо, если я использую следующую специализацию шаблона

template<>
void fun<A>(A) {
    std::cout << "A";
}

вместо перегрузки без шаблона вывод всегда будет AA, независимо от того, поместил ли я специализацию до или после определения caller.

Думайте об этом не с точки зрения времени, а с точки зрения пространства: ном-зависимые вызовы выполняют поиск с того места, где они появляются (только), поэтому учитываются только более ранние объявления.

Davis Herring 25.12.2020 18:21

Я подозреваю, что замена нешаблонной перегрузки специализацией шаблона при сохранении этой специализации ниже вызова fun(A{}) является либо неопределенным поведением, либо неправильно сформированным отчетом о недоставке. Если я правильно помню, специализации должны быть объявлены до первого вызова, который использовал бы их, если бы они были видны.

Igor Tandetnik 25.12.2020 18:32

@IgorTandetnik, думаю, вы правы: раздел Подробно здесь тому подтверждение.

Enlico 26.12.2020 07:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

fun(A{}); не включает никаких выражений, зависящих от параметров шаблона, поэтому поиск и разрешение перегрузки происходят в точке определения. В этот момент виден только fun(T); fun(A) не участвует в разрешении перегрузок.

fun(t)вызов зависит от T, поэтому разрешение откладывается до момента инстанцирования. В этот момент видны как fun(A), так и fun(T), и fun(A) побеждает: не-шаблоны предпочтительнее шаблонов, при прочих равных условиях.

Просто чтобы добавить к ответу Игоря Тандетника: если вы откладываете создание экземпляра, предоставляя поддельные параметры, разрешение перегрузки задерживается:

#include <iostream>
#include <type_traits>

struct A {};

template<typename T>
void fun(T) {
    std::cout << "T";
}

template<typename T, typename AA = A> // <<==== HERE
void caller(T t) {
    fun(AA{});
    fun(t);
}

void fun(A) {
    std::cout << "A";
}

int main() {
    caller(A{}); // Does AA
}

Это обычная уловка для задержки разрешения выражений, не зависящих от параметров шаблона, например, добавление поддельных параметров в шаблоны для предотвращения заполнения специализаций, например:

template<typename T>
struct foo {};

template<>
struct foo<void> {}; // Instantiated immediately

template<typename T, int fake = 0>
struct bar {};

template<int fake>
struct bar<void, fake> {}; // Not instantiated until used

// Users of bar<T> almost never realize they use bar<T, 0>, you can even provide a wrapper without `int` parameter.

Иногда вам нужно получить доступ к члену класса шаблона с помощью this->, чтобы заставить их также быть зависимыми и т. д.

Другие вопросы по теме