Комментарии 14
Обобщённое программирование — ключевое преимущество C++. Я знаю не все языки, но ничего подобного и на таком уровне не видел.
Если вы про именно обобщённое программирование, то какой-нибудь Haskell уделывает С++ и по удобству, и по продуктивности. И появление концептов пусть и улучшит ситуацию, но ненамного. Как минимум потому что они необязательные.
Если же вы про метапрограммирование, то примеров гораздо лучших решений хватает. Был такой язык Nemerle. Который позволял полноценное метапрограммирование с использованием квазицитат и паттерн-матчинга по частям AST. Ещё из недавно виденного — Zig и его comptime, который позволяет, к примеру, генерить обобщённые типы в отсутствии дженериков. Тот же Template Haskell, если не ошибаюсь.
Вообще же, шаблоны С++ как инструмент обобщённого или метапрограммирования далеко не предел. И далеко не идеал.
Спасибо! Да, стоило добавить, что имеются ввиду императивные строго типизированные языки и именно обобщенное, а не метапрограммирование. В функциональном мире слишком много всего интересного.
Что касается второго, то в кругах C++ активно обсуждается введение рефлексии.
Да, то что я пишу, что я другого такого языка не знаю, не значит, что его нет. Я знаю далеко не все языки)
Однако у обобщённого программирования в C++ есть огромный минус: возникающие ошибки — это боль.
Ещё одна боль метапрограммирования в C++ — это отладка. Нужно либо досконально знать логику работы компилятора при обработке шаблоного кода, либо довольствоваться верой в то, что ваша метапрограмма будет компилироваться правильно при всех условиях. На сколько я понимаю, все средства отладки ограничиваются, условно говоря, тестовой печатью.
Плюсовый компилятор, если не вдаваться в подробности, состоит из собственно компилятора C++ (без шаблонов) и интерпретатора мета-языка шаблонов. И если с отладкой исполняемого кода всё более или менее в порядке, то отлдка мета-языка находится где-то на уровне 60-х годов прошлого века: на входе колода перфокарт, на выходе телетайп. Как-то посмотреть промежуточные результаты вычислений интерпретатора весьма затруднительно. Не говоря уже про точки останова и прочие плюшки отладчика.
Конечно отладка — это не вопрос Стандарта. Но и производители компиляторов и IDE не спешат что-то делать в этом направлении. (А прошло, на минуточку, уже больше 30 лет с момента появления первого компилятора C++.) Поэтому метапрограммирования на C++ стараются, по возможности, избегать (иногда даже запрещать на уровне корпоративного code-style). А когда это невозможно, метапрограммные части кода являют собой достаточно примитивные конструкции, правильность которых можно оценить невооружённым глазом. И только малый процент мета-кода является действительно сложными программами на мета-языке.
Надо ещё сказать, что форк Apple llvm, несмотря на версию компилятора clang 12.0, поддерживает концепты на уровне синтаксиса (ещё бы, компилятор-то взяли свежий), но не стандартной библиотеки!
Чем это объяснить, кроме ударенности эппловцев, я не знаю.
По поводу эпитета. Не хватает промежуточного между "супер" и "так себе".
С одной стороны, фича крутая. А с другой, она уже принесла горюшко: дефект в ABI. Авторы всех известных компиляторов забыли/забили декорировать ограничения в именах функций.
И я их, в принципе, понимаю. Одно дело, когда у тебя есть кортеж типов — аргументов функции, плюс кортеж параметров шаблона.
И другое — когда у тебя синтаксическое дерево ограничений! Которое надо сравнивать с точностью до несущественных имён, а то и до перестановок. А может быть, и до ассоциативности дизъюнкций-конъюнкций…
И вот это вот всё добро нужно компактно закодировать в строку.
Представим себе, что у нас (в нескольких единицах трансляции) есть вот такое:
template<class T> concept Foo = alfa<T> && beta<T>;
template<Foo T> void f();
template<class T> concept Bar = alfa<T> && beta<T>;
template<class T> void f() requires Bar<T>;
template<class T> void f() requires alfa<T> && beta<T>;
Здесь одна и та же перегрузка f() объявлена, или разные?
А вот такое:
template<class T> void f() requires true;
template<class T> void f() requires true && true;
Ну или самое вот такое:
template<class T> void f() requires(T t, T u, T v) { foo(t, u, v); };
template<class U> void f() requires(U t, U u, U v) { foo(u, v, t); };
Оговорка про разные единицы трансляции — к тому, что инстанцирование функции из шаблона не приведёт к неоднозначности.
Но должно или не должно приводить к нарушению ODR, с одной стороны, и к формированию единого адреса функции, с другой стороны?
Как вариант: сперва напишем объявление шаблона, а ниже — эквивалентное определение этого шаблона. Эквивалентное с точностью до чего?
А какие вы видите принципиальные несхождения с traits в Rust? Или просто не смотрели на него?
Стандарт C++20: обзор новых возможностей C++. Часть 3 «Концепты»