Как стать автором
Обновить

Комментарии 34

А я вот не совсем пойму. Зачем эти шаблоны вообще нужны? По-сути, это просто особый вид параметризации, который вычисляется в момент компиляции.
Особенность такой параметризации, что там параметры имеют один тип — ссылка на класс и эта ссылка не меняется динамически (в рантайме).
Но не лучше ли просто сделать более умный компилятор, который будет оптимизировать код, опираясь на константы, и тогда «автоматически» получится и «шаблонизация». Ну, шаблонизации уже не получится, просто будет решена та же задача, которую решает шаблонизация — получение абстрактных алгоритмов и структур данных.
Кроме умного компилятора, потребуется еще одно: умение использовать rvalue в качестве декларации типа:
T foo(type T) {
  T* t = new T()
  return t
}


Типа такого. Все остальное может разрулить компилятор.

Я где-то заблуждаюсь?
А что такое «умение использовать rvalue в качестве декларации типа»?
В вашем примере по сути тот же шаблон, только шаблонные аргументы объединены с обычными.
Я кстати давно задумывался над тем, лучше был бы такой синтаксис (и следующая из него семантика) или хуже. Пока не придумал:)
func foo(type T, const int N, int x)
против
func foo<type T, int N>(int x)
А что такое «умение использовать rvalue в качестве декларации типа»?

Ну там, где обычно указывается тип, можно указать выражение.

Там, конечно, нужны еще дополнительные ограничения. Не любое выражения. Выражение с побочными эффектами — точно нет. Выражение должно быть возможно вычислить на этапе компиляции. Но, в целом, это все решаемо.

В вашем примере по сути тот же шаблон, только шаблонные аргументы объединены с обычными.

Да. Это то, что по-сути шаблоны и есть. Только я показал, что для них не нужен специальный синтаксис, который бы столь разительно отличался от остального синтаксиса.

func foo(type T, const int N, int x)
против
func foo<type T, int N>(int x)

Это ровно та же самая идея, что и у меня. 1 в 1.
Ну там, где обычно указывается тип, можно указать выражение.

auto или decltype не подойдет? Внутри decltype по идее можно писать хоть с побочными эффектами, этот код все равно не попадает на кодогенерацию.
Или лучше приведите пример как бы это должно выглядеть.
auto или decltype не подойдет?

Ну, что-то типа этого. Только это должно автоматически делаться, без необходимость руками прописывать.

Или лучше приведите пример как бы это должно выглядеть.

Я же привел:
T* foo(type T) {
  T* t = new T()
  return t
}
А я вот не совсем пойму. Зачем эти шаблоны вообще нужны?

Когда-то давно-давно, разбирая исходники PHP4 из-за необходимости внести пару мелких правок в пул odbc-коннектов, я задавался тем же вопросом, разбирая шаблоны семиуровневой вложенности. Я тогда искренне возненавидел и тех, кто их придумал, и тех, кто открыл метапрограммирование с их помощью, и тех, кто это написал :)
В итоге быстрее оказалось написать собственное расширение РНР, с собственной реализацией odbc_pconnect

golang'исты с вами всеми согласны. Генерализация плохо — проще заняться кодогенерацией по шаблону )))

Не надо ёрничать. Проблема не в обобщениях как таковых, а в применении их не по назначению. Есть языки с работающими type traits, в которых не возникает этой каши. И есть языки с вменяемым метапрограммированием через квази-цитирование.

Шаблоны придумал Страуструпп, кстати, по словам математиков очень круто сделал.
habr.com/ru/post/166849
habr.com/ru/post/167257
А дальше, получилось, что «джина выпустили из бутылки»…
У меня бывало, что разобраться с библиотекой на Си, гораздо проще, чем с аналогичной реализацией на С++.

Одна из цитат Линуса
another case of C++ people thinking too much «this is a
cool feature» without then actually doing it well.

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

Но потом кто-то придумал, что молотком можно забивать шурупы. Так появилось "метапрограммирование"...

НЛО прилетело и опубликовало эту надпись здесь

Это, конечно, не про C++, но вот Zig так может.

Всё просто. Шаблоны имеют свои особые эффекты:
1) Увеличение ресурсов компиляции
2) Каждая новая инстанция шаблона это отдельная полноценная сущность (функция или класс). Это приводит например к тому, что адреса двух инстанций одной шаблонной функции неравны.
3) Следствие из п.2: раздувание бинарника.

Если вы уберёте все эти эффекты в неявный синтаксис, то огребёте потом отлаживать код (особенно п.2).
Во многих других языках задачу параметризации типов решают с помощью дженериков сиречь обобщений. Как по мне, более практичный способ, который
а) не раздувает бинарник
б) успешно решает задачу параметризации
в) не даёт лишней возможности сделать это нечитабельным

Не раздувается бинарник разве что на управляемых языках, где в "бинарнике" хранится промежуточный байт-код.


Тут основной "затык" — именно в возможности взять адрес у функции.

С шаблонами и так код не слишком просто отлаживается.

А как п.2 ведет к проблемам с отладкой я не понял. Это же по-сути типа обычная функция. А обычные функции хорошо отлаживаются и с ними нет проблем.

Если кому-то правда нужно брать указатели на функции и понимать разные они или нет — он всегда может сделать функцию-обертку.
Указатели на функцию это один из кейсов. Как насчёт статических переменных? В каждой отдельной инстанции у нас создаётся своя переменная. Это поведение отличается от дженериков из с#, например. Такие эффекты недопустимо делать неявно.
Ну, шарписты же как-то живут с этим. Наверное, и плюсовики справятся.

Для дженериков можно сделать ADT и на их базе все решать.

В принципе, вопрос со статическими переменными можно решить на базе Map-ов (псевдокод):

class Foo (type T) {
static Bar = Map(subclassof Foo, T)
}


Ну и для красоты снабдить это отдельным синтаксисом:

class Foo (type T) {
static instance T Bar
}


В общем, решаемо.

Статические переменные генерик классов точно так же дублируются, на это емнип есть даже ворнинг.

> И забавный синтаксис с повторяющимися requires, который реально путает. Неужели в английском языке так мало слов, что пришлось использовать одно слово для совершенно разных целей?

вопрос в том, сколько написанного кода сломается, потому что кто-то использовал `requires` как тип или название функции. Если выбрать 2 слова для схожих целей — тогда кода сломается в 2 раза больше.
Это контекстное ключевое слово. Оно работает только внутри template в таком месте, где раньше никакого кода быть не могло.
К тому же, если код сломается и перестанет компилироваться, то его можно исправить. А если мозг программиста сломается, то кто будет исправлять? :)))

Это не правда, requires не контекстный, и использовать requires-expression не обязательно внутри шаблонов.

Ок, давайте поиграем в игру "в интернете кто-то не прав".
Во-первых, в C++ контекстных кейвордов, как в том анекдоте (Сначала было мало. Пришли мы с женой module c import, стало вдвое больше). В частности, requires не контекстный keyword в этом легко убедиться, проверив компилируемость int requires; https://gcc.godbolt.org/z/b39cvz:


int final;      // OK
int override;   // OK
int module;     // OK
int import;     // OK
int requires;   // Fail

Во-вторых, requires-expression может использоваться где угодно, к примеру (https://gcc.godbolt.org/z/zHRSg5)


bool b = requires { 2 + 2; };

Возможно, вас смутило, то что это пока не работает в MSVC, но они об этом честно предупреждали, анонсируя свою поддержку концептов.

вопрос в том, сколько написанного кода сломается

У нас в проекте был такой казус со словом override при переходе на c++11. Ничего страшного не случилось, поправили. Теперь override по назначению используем.

наконец-то, давно пора было это ввести
НЛО прилетело и опубликовало эту надпись здесь

Неужели вот так не работает?


if constexpr (Procedure<F>) {
  ...
}

Конечно, работает.

НЛО прилетело и опубликовало эту надпись здесь

Поскольку концепт — это именованный предикат на шаблонных параметрах, который вычисляется в compile-time, его, конечно же, можно использовать в constexpr if. К тому, что вам уже ответили, добавлю от себя, что определить такой концепт можно значительно проще:


template<typename F, typename... Args>
concept procedure = std::is_invocable_r_v<void, F, Args...>;

Используется точно так же:


if constexpr (procedure<void(int), int>) { /* compiled */ }
Нужно ли знать темплейты, чтобы пользоваться концептами?
Странный вопрос. Сейчас нужно знать темплейты, чтобы вообще пользоваться С++.
Я давно не работал в плюсах, и наткнулся на лекцию о концептах. Показалось что это полностью другой язык нежели stl. Вот и думаю если снова вкатываться в плюсы, учить ли темплейты или сразу навернуть концепты.

Концепт — это просто условие для темплейта. В отрыве от темплейтов они почти бессмысленны.


А вот что и правда можно больше не учить (ну, пока вам не понадобится работать с легаси, хе-хе) — это SFINAE-трюки и всяческие enable_if.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации