Информация об изменениях

Сообщение Re: Для чего шаблонной функции нужна особая сигнатура? от 25.01.2023 17:47

Изменено 25.01.2023 17:51 B0FEE664

Re: Для чего шаблонной функции нужна особая сигнатура?
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Чем функция с определенными типами параметров и результата, реализованная через шаблон, в плане линковки отличается от функции с теми же типами, но без шаблона?


Рассмотрим полную специализацию (худых специализаций не бывает) шаблонной функции:
template<> void f<>(char* p);

Такая декларация нам говорит, что где-то объявлена шаблонная функция, но как именно она выглядит мы не знаем, так как декларации функции возможны разные. Более того, возможны даже некоторые их комбинации за счёт перегрузок.
Теперь рассмотрим вызов этой функции:
char* p = nullptr;
f(p);

Зададимся вопросом: существует ли такой код, для которого этот вызов не будет вызовом функции template<> void f<>(char* p);?
Я, таки, напишу это: не всё так однозначно!
Но всё логично.
Дело в том, что прежде всего из всего набора функций f, нам нужно выбрать ту, что наиболее близко подходит по параметру к указанному вызову.
допустим изначально у нас есть две перегрузки функции f, :
такая:
template<class T> void f(T x); // 1

и такая:
template<class T> void f(T* pX); // 2

а исходной специализации ещё нет:
template<> void f<>(char* p);
Надеюсь вы согласитесь, что вызов
char* p = nullptr;
f(p);

должен вызвать функцию номер 2: template<class T> void f(T* pX);
Т.е. следующий код напечатает p:
#include <iostream>
template<class T> void f(T pX)  {std::cout << "t\n";}
template<class T> void f(T* pX)  {std::cout << "p\n";}
int main()
{
  char* p = nullptr;
  f(p);
  return 0;      
}


А теперь добавим вызов f до второй перегрузки:
#include <iostream>
template<class T> void f(T pX)  {std::cout << "t\n";}

void g()
{
  char* p =nullptr;
  f(p);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
int main()
{
  char* p =nullptr;
  f(p);
  g();
  return 0;      
}


В функции g() при вызове f(p); ничего о template<class T> void f(T* pX) не известно, поэтому в выводе мы увидим
t
p


Если вы думаете, что тут что-то не так, то проверим тот же код с обычными функциями:
#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

Из g() будет вызвана функция void old(short n), а из main() — void old(int f), хотя вызовы совершенно одинаковы old(0);!
Вывод:
t
short
p
int


Таким образом поведение шаблонных функций соответствует поведению обычных функций.

А теперь добавим специализацию шаблонной функции:
#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}
template<> void f<>(char* p) {std::cout << "char* spec 1\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

Вывод:
char* spec 1
short
p
int


Как вы видите, вызов f(p); из main(), не вызывает template<> void f<>(char* p), а вызывает template<class T> void f(T* pX). Вызывается шаблонный вариант так как, при выборе из двух функций template<class T> void f(T pX) и template<class T> void f(T* pX), вторая подходит лучше, а для нее специализации нет, так как специализация сделана только для первой функции. Как видите при линковке перегрузка шаблонной функции может скрывать специализацию шаблонной функции, а вот скрыть перегрузку обычной функции шаблонная функция не может:

#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}
template<> void f(char* p) {std::cout << "char* spec 1\n";}
void f(char* p) {std::cout << "simple char*\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
template<> void f<>(char* p) {std::cout << "char* spec 2\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

simple char*
short
simple char*
int

А раз функции линкуются по разному, значит они должны иметь разные сигнатуры.
Re: Для чего шаблонной функции нужна особая сигнатура?
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Чем функция с определенными типами параметров и результата, реализованная через шаблон, в плане линковки отличается от функции с теми же типами, но без шаблона?


Рассмотрим полную специализацию (худых специализаций не бывает) шаблонной функции:
template<> void f<>(char* p);

Такая декларация нам говорит, что где-то объявлена шаблонная функция, но как именно она выглядит мы не знаем, так как декларации функции возможны разные. Более того, возможны даже некоторые их комбинации за счёт перегрузок.
Теперь рассмотрим вызов этой функции:
char* p = nullptr;
f(p);

Зададимся вопросом: существует ли такой код, для которого этот вызов не будет вызовом функции template<> void f<>(char* p);?
Я, таки, напишу это: не всё так однозначно!
Но всё логично.
Дело в том, что прежде всего из всего набора функций f, нам нужно выбрать ту, что наиболее близко подходит по параметру к указанному вызову.
допустим, изначально у нас есть две перегрузки функции f:
такая:
template<class T> void f(T x); // 1

и такая:
template<class T> void f(T* pX); // 2

а исходной специализации ещё нет:
template<> void f<>(char* p);
Надеюсь вы согласитесь, что вызов:
char* p = nullptr;
f(p);

должен вызвать функцию номер 2: template<class T> void f(T* pX);
Т.е. следующий код напечатает p:
#include <iostream>
template<class T> void f(T pX)  {std::cout << "t\n";}
template<class T> void f(T* pX)  {std::cout << "p\n";}
int main()
{
  char* p = nullptr;
  f(p);
  return 0;      
}


А теперь добавим вызов f до второй перегрузки:
#include <iostream>
template<class T> void f(T pX)  {std::cout << "t\n";}

void g()
{
  char* p =nullptr;
  f(p);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
int main()
{
  char* p =nullptr;
  f(p);
  g();
  return 0;      
}


В функции g() при вызове f(p); ничего о template<class T> void f(T* pX) не известно, поэтому в выводе мы увидим
t
p


Если вы думаете, что тут что-то не так, то проверим тот же код с обычными функциями:
#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

Из g() будет вызвана функция void old(short n), а из main() — void old(int f), хотя вызовы совершенно одинаковы old(0);!
Вывод:
t
short
p
int


Таким образом поведение шаблонных функций соответствует поведению обычных функций.

А теперь добавим специализацию шаблонной функции:
#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}
template<> void f<>(char* p) {std::cout << "char* spec 1\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

Вывод:
char* spec 1
short
p
int


Как вы видите, вызов f(p); из main(), не вызывает template<> void f<>(char* p), а вызывает template<class T> void f(T* pX). Вызывается шаблонный вариант так как, при выборе из двух функций template<class T> void f(T pX) и template<class T> void f(T* pX), вторая подходит лучше, а для нее специализации нет, так как специализация сделана только для первой функции. Как видите, при линковке перегрузка шаблонной функции может скрывать специализацию шаблонной функции, а вот скрыть перегрузку обычной функции шаблонная функция не может:

#include <iostream>
void old(short n)  {std::cout << "short\n";}
template<class T> void f(T pX)  {std::cout << "t\n";}
template<> void f(char* p) {std::cout << "char* spec 1\n";}
void f(char* p) {std::cout << "simple char*\n";}

void g()
{
  char* p =nullptr;
  f(p);
  old(0);
}

template<class T> void f(T* pX)  {std::cout << "p\n";}
template<> void f<>(char* p) {std::cout << "char* spec 2\n";}
void old(int f)  {std::cout << "int\n";}
int main()
{
  g();
  char* p =nullptr;
  f(p);
  old(0);
  return 0;      
}

simple char*
short
simple char*
int

А раз функции линкуются по разному, значит они должны иметь разные сигнатуры.