Комментарии 34
Особенность такой параметризации, что там параметры имеют один тип — ссылка на класс и эта ссылка не меняется динамически (в рантайме).
Но не лучше ли просто сделать более умный компилятор, который будет оптимизировать код, опираясь на константы, и тогда «автоматически» получится и «шаблонизация». Ну, шаблонизации уже не получится, просто будет решена та же задача, которую решает шаблонизация — получение абстрактных алгоритмов и структур данных.
Кроме умного компилятора, потребуется еще одно: умение использовать rvalue в качестве декларации типа:
T foo(type T) {
T* t = new T()
return t
}
Типа такого. Все остальное может разрулить компилятор.
Я где-то заблуждаюсь?
В вашем примере по сути тот же шаблон, только шаблонные аргументы объединены с обычными.
Я кстати давно задумывался над тем, лучше был бы такой синтаксис (и следующая из него семантика) или хуже. Пока не придумал:)
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 по идее можно писать хоть с побочными эффектами, этот код все равно не попадает на кодогенерацию.
Или лучше приведите пример как бы это должно выглядеть.
А я вот не совсем пойму. Зачем эти шаблоны вообще нужны?
Когда-то давно-давно, разбирая исходники PHP4 из-за необходимости внести пару мелких правок в пул odbc-коннектов, я задавался тем же вопросом, разбирая шаблоны семиуровневой вложенности. Я тогда искренне возненавидел и тех, кто их придумал, и тех, кто открыл метапрограммирование с их помощью, и тех, кто это написал :)
В итоге быстрее оказалось написать собственное расширение РНР, с собственной реализацией odbc_pconnect
golang'исты с вами всеми согласны. Генерализация плохо — проще заняться кодогенерацией по шаблону )))
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` как тип или название функции. Если выбрать 2 слова для схожих целей — тогда кода сломается в 2 раза больше.
К тому же, если код сломается и перестанет компилироваться, то его можно исправить. А если мозг программиста сломается, то кто будет исправлять? :)))
Это не правда, 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 */ }
Первое впечатление от концептов