Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 16:50
Оценка:
// header.h
template<class T> void f(T);

//template<> void f(int);

inline void fi(int x) { f(x); }

#include "header.h"

template<> void f(int x) {}


Почему без явного объявления f(int) имеем грабли?
Зачем так сделали?
Re: Вопрос знатокам
От: T4r4sB Россия  
Дата: 03.04.23 17:04
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Почему без явного объявления f(int) имеем грабли?

_>Зачем так сделали?

Какие грабли-то?

Только что на рекстестере проверил, всё ок. (хз как там расшарить результат)
Re: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 17:28
Оценка: 3 (1) +3
Здравствуйте, kov_serg, Вы писали:

_>
_>// header.h
_>template<class T> void f(T);

_>//template<> void f(int);

_>inline void fi(int x) { f(x); }
_>

_>
_>#include "header.h"

_>template<> void f(int x) {}
_>


_>Почему без явного объявления f(int) имеем грабли?


Таковы требования стандарта, явная специализация должна быть видна в точке использования:

https://timsong-cpp.github.io/cppwp/temp.expl.spec#7

If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit specialization that is declared but not defined.


_>Зачем так сделали?


Ну, наверное для того, чтоб компилятор не пытался выполнить инстанцирование праймари шаблона.

В документации по msvc есть вот такое описание ошибки: https://learn.microsoft.com/en-us/cpp/error-messages/compiler-errors-2/compiler-error-c2908?f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(C2908)%3Bk(vs.output)%26rd%3Dtrue&amp;view=msvc-170

P.S. То, что в этом примере используется шаблон класса, а не шаблон функции, ничего по сути не меняет — требование общее.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.04.2023 17:43 rg45 . Предыдущая версия .
Re[2]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 20:00
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну, наверное для того, чтоб компилятор не пытался выполнить инстанцирование праймари шаблона.

// header.h
template<class T> void f(T);

//template<> void f(int);

inline void fi(int x) { f(x); }

#include "header.h"

// template<> void f(int x) {}

Зато в таком случае компилируется, но не может слинковать — нет реализации:
Отредактировано 03.04.2023 20:01 kov_serg . Предыдущая версия .
Re[3]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 20:34
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>
_>// header.h
_>template<class T> void f(T);

_>//template<> void f(int);

_>inline void fi(int x) { f(x); }
_>

_>
_>#include "header.h"

_>// template<> void f(int x) {}
_>


_>Зато в таком случае компилируется, но не может слинковать — нет реализации:


И что тебе кажется неправильным во втором случае? С точки зрения компилятора, каждая единица трансляции и программа в целом well-formed. Просто отсутствует объектный файл, содержащий используемое воплощение праймари шаблона.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 20:43
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>
_>// header.h
_>template<class T> void f(T);

_>//template<> void f(int);

_>inline void fi(int x) { f(x); }
_>

_>
_>#include "header.h"

_>// template<> void f(int x) {}
_>

_>Зато в таком случае компилируется, но не может слинковать — нет реализации:


Я больше скажу: если ты вынесешь специализацию в отдельный cpp-файл и не будешь подключать в этот файл никаких больше заголовков, вот так:

template<class T> void f(T); // повторное объявление вместо подключения заголовка

template<> void f(int x) {}


этим самым ты лишишь компилятор возможности продиагностировать нарушение требований и программа может даже успешно скомпилироваться. Вполне возможно, что и линкер успешно отработает и программу можно будет запустить на выполнение (я проверил на msvc — именно так все и работает). Но все равно программа при этом будет ill-formed.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.04.2023 20:49 rg45 . Предыдущая версия . Еще …
Отредактировано 03.04.2023 20:44 rg45 . Предыдущая версия .
Re[4]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 20:49
Оценка: +1 :)
Здравствуйте, rg45, Вы писали:

R>И что тебе кажется неправильным во втором случае? С точки зрения компилятора, каждая единица трансляции и программа в целом well-formed. Просто отсутствует объектный файл, содержащий используемое воплощение праймари шаблона.

Если добавить отдельный файл. То собирается.
// another_translation_unit.cpp
template<class T> void f(T);
template<> void f(int x) {}

Мне кажется такое поведение слегка %6@нутым неправильным.
Re[5]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 20:50
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Мне кажется такое поведение слегка %6@нутым неправильным.


Есть предложения?

P.S. Ты попробуй посмотреть на ситуацию с точки зрения разработчика компилятора: у тебя есть объявление шаблона и его использование. Тебе нужно принять решение нужно ли в точке использования делать инстанцирование основного шаблона или вызвать какую-то из существующих явных специализаций. Как ты будешь принимать это решение?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.04.2023 20:53 rg45 . Предыдущая версия .
Re[4]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 20:52
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я больше скажу: если ты вынесешь специализацию в отдельный cpp-файл и не будешь подключать в этот файл никаких больше заголовков, вот так:

R>этим самым ты лишишь компилятор возможности продиагностировать нарушение требований и программа может даже успешно скомпилироваться. Вполне возможно, что и линкер успешно отработает и программу можно будет запустить на выполнение (я проверил на msvc — именно так все и работает). Но все равно программа при этом будет ill-formed.
Я сделал примерно так:
template<class T>void f(T);

template<> void f(int); inline void fi(int x) { f(x); }

template<> void f(int x) {}

Просто выглядит вырвиглазно.
Re[6]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 20:59
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Есть предложения?

Напиться.

R>P.S. Ты попробуй посмотреть на ситуацию с точки зрения разработчика компилятора: у тебя есть объявление шаблона и его использование. Тебе нужно принять решение нужно ли в точке использования делать инстанцирование основного шаблона или вызвать какую-то из существующих явных специализаций. Как ты будешь принимать это решение?


С точки зрения компилятора, я не инстанцирую класс, а вызываю функцию. Что явно указано в коде. Нахрена запрещать определение реализации дальше по коду. Почему это не эквивалентно костылю с предварительным насильным объявлением сигнатуры функции.

ps:
void ok() { void fn(); fn(); }
void fail(int x) { template<> void f(int); f(x); } // с чем связан запрет подобных объявлений?
Отредактировано 03.04.2023 21:05 kov_serg . Предыдущая версия .
Re[5]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 21:01
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Я сделал примерно так:

_>
_>template<class T>void f(T);

_>template<> void f(int); inline void fi(int x) { f(x); }

_>template<> void f(int x) {}
_>

_>Просто выглядит вырвиглазно.

Ну тут мне трудно что-либо сказать, не зная задачи, которую ты решаешь и не видя картины в целом. Лишь пара общих соображений. Во-первых, ИМХО(!) код выглядел бы более удобочитаемым, если бы не твое стремление записысывать отдельные языковые конструкции в одну строчку. Во-вторых, ориентируясь на свой опыт, мне трудно вспомнить случай, когда специализация шаблона функции была бы целесообразна (при условии, что весь код твой). Я считаю, что практически всегда вместо специализации можно использовать перегрузку и это будет лучше. Лично у меня любое использование специализаций шаблонов функций вызывает подозрение на не очень удачный дизайн.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[6]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 21:16
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Ну тут мне трудно что-либо сказать, не зная задачи, которую ты решаешь и не видя картины в целом. Лишь пара общих соображений. Во-первых, ИМХО(!) код выглядел бы более удобочитаемым, если бы не твое стремление записысывать отдельные языковые конструкции в одну строчку. Во-вторых, ориентируясь на свой опыт, мне трудно вспомнить случай, когда специализация шаблона функции была бы целесообразна (при условии, что весь код твой). Я считаю, что практически всегда вместо специализации можно использовать перегрузку и это будет лучше. Лично у меня любое использование специализаций шаблонов функций вызывает подозрение на не очень удачный дизайн.

Просто каждая строка отдельное действие. Но это не суть.

Всплыло это случайно при сериализации.
struct Helper {
  Tape &tape; bool store;
  template<class T> void process(T &t);
  template<class T> Helper& operator<< (T &t) { process(t); return *this; }
  Helper(Tape &tape,bool store,int id) : tape(tape), store(store) { process(id); }
};
Re[7]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 21:26
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>С точки зрения компилятора, я не инстанцирую класс, а вызываю функцию. Что явно указано в коде. Нахрена запрещать определение реализации дальше по коду. Почему это не эквивалентно костылю с предварительным насильным объявлением сигнатуры функции.


Мне кажется, что сейчас твое внимание сконцентрировано на конкретном случае и это мешает тебе рассмотреть другие возможные варианты. Допустим, все реализовано как ты хочешь: в точке использования компилятор видит только лишь объявление основного шаблона и шьет такой код, который позволяет линкеру подшить нужную специализацию в точку вызова. Но что если после этого программист решает переместить определение основного шаблона в заголовок? Тогда, после обработки сопроцессором всех директив компилятор получит такой код:

template<class T>
void f(T t)
{
  // Do smth with t
}

void fi(int x)
{
  f(x);
}


Поведение программы должно измениться или нет? Наверное, нет, иначе это будет действительно странное поведение. Но как тогда в точке вызова компилятор должен понять, что здесь ему не нужно инстанцировать основной шаблон? Как быть с неявным инстанцированием, которое обеспечивает именно компилятор — отменяем?

Второй момент: что если это не int, а какой-то сложный тип (например, использующий наследование) под который подходят сразу несколько специализаций, которые к тому же и не видны в данной точке? Что у нас с будет с overload resolution в твоем подходе? Не перекладывать же эту работу на линкер?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.04.2023 21:40 rg45 . Предыдущая версия . Еще …
Отредактировано 03.04.2023 21:29 rg45 . Предыдущая версия .
Отредактировано 03.04.2023 21:27 rg45 . Предыдущая версия .
Отредактировано 03.04.2023 21:27 rg45 . Предыдущая версия .
Re[8]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 21:40
Оценка:
Здравствуйте, rg45, Вы писали:


R>Мне кажется, что сейчас твое внимание сконцентрировано на конкретном случае и это мешает тебе рассмотреть другие возможные варианты. Допустим, все реализовано как ты хочешь: в точке использования компилятор видит только лишь объявление основного шаблона и шьет такой код, который позволяет линкеру подшить нужную специализацию в точку вызова.


R>Но что если после этого программист решает переместить определение основного шаблона в заголовок? Тогда, после обработки сопроцессором всех директив компилятор получит такой код:


R>
R>template<class T>
R>void f(T t)
R>{
R>  // Do smth with t
R>}

R>void fi(int x)
R>{
R>  f(x);
R>}
R>

Вот когда он это сделает то ситуация уже другая. Тут есть что инстанцировать. До этого было только объявление без дефолтной реализации, вообще без какой либо реализации совсем.

R>Как в точке вызова компилятор должен понять, что здесь ему не нужно инстанцировать основной шаблон? Как быть с неявным инстанцированием, которое обеспечивает именно компилятор — отменяем?

В предыдущем варианте небыло ни строчки про то что надо что-либо инстанцировать, только объявление.

R>Второй момент: что если это не int, а какой-то сложный тип (например, использующий наследование) под который подходят сразу несколько специализаций, которые к тому же и не видны в данной точке? Что у нас с будет с overload resolution в твоем подходе? Не перекладывать же эту работу на линкер?

В чем собственно разница? Всё должно работать единообразно, но это не про стандарт c++
Re[9]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 21:43
Оценка:
Здравствуйте, kov_serg, Вы писали:

R>>
R>>template<class T>
R>>void f(T t)
R>>{
R>>  // Do smth with t
R>>}

R>>void fi(int x)
R>>{
R>>  f(x);
R>>}
R>>

_>Вот когда он это сделает то ситуация уже другая. Тут есть что инстанцировать. До этого было только объявление без дефолтной реализации, вообще без какой либо реализации совсем.

Ну вот это неочевидный вывод, для меня, по крайней мере. Если при перемещении определения шаблона функции из срр-файла в заголовок будет меняться видимое поведение программы, то это уже действительно странно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[9]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 21:46
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>В предыдущем варианте небыло ни строчки про то что надо что-либо инстанцировать, только объявление.


И тем не менее, это определение может существовать где-то в соседней единице трансляции. Если его нет сейчас, то его можно добавить в любой момент.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[10]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 21:53
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну вот это неочевидный вывод, для меня, по крайней мере. Если при перемещении определения шаблона функции из срр-файла в заголовок будет меняться видимое поведение программы, то это уже действительно странно.

Чего тут странного. Поведение не должно меняться.
template<class T> void f0(T&);
template<class T> void f1(T&) {}

inline void f0i(int x) { f0(x); }
inline void f1i(int x) { f1(x); }
//...
// template<> void f1(int&) {} // ошибка, уже есть реализация, полученная при инстанцировании
// template<> void f0(int&) {} // ошибка, просто за компанию. реализаци не ну и пофиг всё равно нельзя.

Надо явно компилятору дополнительно пальцем тыкать. Как буд-то он не в курсе что у него нет никакой дефолтной имлементации.
template<class T> void f0(T&);
template<class T> void f1(T&) {}

template<> void f0(int &); inline void f0i(int x) { f0(x); }
inline void f1i(int x) { f1(x); }
//...
// template<> void f1(int&) {} // ошибка, уже есть реализация, полученная при инстанцировании
template<> void f0(int&) {} // теперь можно

Разве не видно неконсистентность?
Re[10]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 21:57
Оценка:
Здравствуйте, rg45, Вы писали:

R>И тем не менее, это определение может существовать где-то в соседней единице трансляции. Если его нет сейчас, то его можно добавить в любой момент.

template<class T>void f(T t);
template<class T>void g(T t);

inline void fi(int x) { f(x); }

template<class T>void f(T t) { g(t);}
template<> void g(int x) {}

Имеется ввиду так?
Re[11]: Вопрос знатокам
От: rg45 СССР  
Дата: 03.04.23 22:19
Оценка:
Здравствуйте, kov_serg, Вы писали:

R>>И тем не менее, это определение может существовать где-то в соседней единице трансляции. Если его нет сейчас, то его можно добавить в любой момент.

_>
_>template<class T>void f(T t);
_>template<class T>void g(T t);

_>inline void fi(int x) { f(x); }

_>template<class T>void f(T t) { g(t);}
_>template<> void g(int x) {}
_>

_>Имеется ввиду так?

Примерно так:

// header.h

template<class T> void f(T t);

inline void fi(int x) { f(x); }


// main.cpp
#include header.h

int main()
{
   fi(42);
}


// separate.cpp

// Primary template definition
template<typename T>
void f(T t)
{
   // . . .
}

// Explicit template instantiations
template void f<char>(char);
template void f<short>(short);
template void f<double>(double);

// Explicit specialization for f<int>
template<>
void f<int>(int i)
{
   // . . .
}


Если собрать этот проект под msvc, то будет работать в точности как ты хочешь. Но потом кто-то решает, что нужно перенести определение основного шаблона функции f из separate.cpp в header.h. При этом специализация f<int> остается в separate.cpp и в точке использования в fi по-прежнему не видна. Должно измениться поведение программы после такого перенесения или нет?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.04.2023 22:25 rg45 . Предыдущая версия .
Re[12]: Вопрос знатокам
От: kov_serg Россия  
Дата: 03.04.23 22:34
Оценка:
Здравствуйте, rg45, Вы писали:


R>Если собрать этот проект под msvc, то будет работать в точности как ты хочешь. Но потом кто-то решает, что нужно перенести определение основного шаблона функции f из separate.cpp в header.h. При этом специализация f<int> остается в separate.cpp и в точке использования в fi по-прежнему не видна. Должно измениться поведение программы после такого перенесения или нет?

Так много вариантов есть всё переломать. Это один из них. Тут ситуация я так понял проще.
template<class T>void f(T); // заявляем что есть скоро будетет такой дефолтный метод

inline void fi(int x) { f(x); } // используем дефолтную реализацию f<int> пофиг что её пока нет, вруг ниже есть

template<class T>void f(T) {} // и тут фигакс вот он. теперь ок. но если мы его не объявим ... то ub (ну вы вкурсе програмист сам дурак по стандарту во всех спорных ситуациях)

Проблемма в том что дефолтный f(T) может быть определён позже и отсюда вся эта лажа и вытекает.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.