cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

make template and typename more optional #499

Open kelbon opened 2 years ago

kelbon commented 2 years ago

Описание: Если разрешить на декларации классов/структур писать концепт и это будет означать, что класс и все специализации класса должны соответствовать этому концепту, то мы получим

  1. Явный шаблонный интерфейс(а не как раньше неявный)
  2. Возможность компилятору понимать какие алиасы/ шаблоны обязаны присутствовать в классе и его специализациях, а значит можно будет не писать typename / template, это позволит сделать нормальный интерфейс тупла(с внутренним гетом), добавить в стандарт базовые инструменты работы с вариадиками(например std::type_list и внутри него шаблоны для работы с паком, например ::containts / expand<Foo > и прочее)
  3. Улучшение читаемости кода
  4. Возможность проверить наличие ошибок на уровне концептов(противоречивых условий в них и ошибки в местах их использующих в шаблонном коде)

Примеры

template<typename...>
std::ranges::random_access_range struct MyClass {
// много кода, из которого долго понимать что перед нами
};

template<typename T>
concept metafunction = requires (T) { typename T::type };

template<typename T>
metafunction struct type_identity { using type = T; };

... где то в другом месте ...
template<metafunction F>
void Foo() {
F::type value; // без typename
}
// или я сделал специализацию класса MyVector и у него итератор
// не соответствует концепту random_access_iterator - ошибка компиляции! Классно же

Или вот другой пример, чего явно не хватает в стандарте - работы с вариадиками(тем более что после добавления в стандарт скорость компиляции подобного кода из-за интринсиков вырастет в сотни раз)

template<typename T>
concept type_list_interface = requires (T) {
 typename T::template get<0>;
 { T::template contains<int> } -> std::same_as<bool>;
}
template<typename...>
type_list_interface struct type_list { ... };

... где в пользовательском коде ...

template<typename... Types>
auto Foo(Types&&... args) {
if constexpr (type_list<Types...>::contains<double>) (
 return 3;
}
}
apolukhin commented 2 years ago

Идея очень интересная и заманчивая. Вот только template/typename нужны чтобы парсер/лексер правильно понимали C++. А парсеры как правило достаточно простые и работают с выражением до ;, зачастую не понимая семантику. На этом уровне зачастую нет приемлемого для парсинга стейта в котором хранятся данные по концептам.

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

kelbon commented 2 years ago

Вот только template/typename нужны чтобы парсер/лексер правильно понимали C++

Да, разумеется, но всё же они знают о декларации некого типа/концепта, по сути компилятору для каждого концепта нужно изобрести тип а также все связанные с этим типом(созданные внутри концепта) специализации и т.д. с ожидаемыми операциями(для них будет известно что это, typename/template и т.д.) и запрещёнными операциями(похоже на = delete) Например

template<typename T>
concept have_no_foo = !requires(std::vector<T> foo) {
    foo.break_compilation(5);
};
template<have_no_foo T>
int foo(std::vector<T> vec) {
    vec.break_compilation(5);
}

Компилятор в точке определения have_no_foo должен изобрести have_no_fooT и специализацию std::vector, в которой запрещен вызов функции break_compilation(int) и выдать ошибку компиляции, после увиденного вызова запрещённой функции С помощью этого механизма по сути можно ещё и находить ошибки в концептах, когда в них находятся противоречивые условия.

Для этого правда нужно ещё правила объединения требований из контрентов, то есть при || или && между констрентами требования всегда объединяются, при этом может получится, что один констрент требует чтобы идентификатор обозначал X, а другой контрент требует что идентификатор обозначал Y, тогда при || имя становится просто снова неоднозначным(а не ожидаемым), а при && возникает конфликт, приводящий к ошибке компиляции.

Если задуматься, то это эквивалентно наследованию итогового изобретённого типа от двух изобретённых типов констрента А и констрента Б То есть для constraintA && / || contraintB struct invented_typeAB : invented_typeA, invented_typeB {}; ( с некоторыми небольшими махинациями )

Согласен, выглядит не супер легко, но и профиты немаленькие Например msvc(интел сенс скорее) уже делает что то похожее(изобретает тип) image