У меня в голове болтается смутное ощущение что новые конструкции типа constexpr, f(auto arg), decltype, if contexpr и concept сводят на нет необходимость старого синтаксиса шаблонов через template<typename .. >, SFINAE и проче с ним, нет? Пока только рассматриваем функции — структуры, видимо, пока ещё требуют старый синтаксис.
Есть ли какие то примеры, которые ещё нельзя переписать целиком, используя новый синтаксис? Что вы думаете?
Кроме того, template<> вариант позволяет явно указать типы в точке вызова, когда компилятор не может сообразить сам, например, если два аргумента имеют общую базу, и надо привести к ней.
Здравствуйте, johny5, Вы писали:
J>Вот накидал пример с перегрузкой (оставим за скобками целесообразность кода, только синтаксис).
J>Было: ... J>Стало: ...
Получились скорее демотивирующие примеры, которые показывают как делать не нужно. И выглядят они неидеоматично.
То есть код работать будет, но это не тот способ использования концептов, который задумывался при их добавлении в язык.
J>requires (!HasFind<decltype(container), decltype(key)>)
Это при SFINAE нужно было следить, чтобы условия под всякими std::enable_if были взаимоисключающими, чтобы не было неоднозначности при нескольких подходящих альтернативах. А ограничения, заданные через requires, уже имеют (частичный) порядок.
То есть не нужно писать
auto find(const auto& container, const auto& key) {...}
Компилятор и так обязан выбрать первую перегрузку (через метод find) в случае, когда обе они подходят, потому что она более ограниченная/специфичная.
Заодно это убирает и момент удивления при чтении кода: "зачем в нём проверяется что-то про наличие метода, если в теле функции ничего такого не используется?". Потому что это было техническое ограничение при реализации через шаблоны. А в концептах почти всегда стоит проверять только те требования к типу, которые действительно нужны.
Во-первых, такое сложно читать — убирание template не упрощает чтение кода.
Во-вторых, в концептах же специально сделана поддержка простой записи ограничений на типы: вместо const auto& container достаточно написать const HasFind<KeyType> auto& container и убрать ручные проверки и секцию require полностью. Компилятор сам подставит тип контейнера в концепт HasFind нужным параметром.
Опять же, в зависимости от сложности выражения это можно использовать как совместно с placeholder'ом auto (как выше), так и внутри template.
И этот способ тоже позволяет компилятору выбирать самому наиболее специфичную перегрузку, если несколько шаблонов подходят под ограничения.
J> concept сводят на нет необходимость старого синтаксиса шаблонов через template<typename .. >
Не совсем.
Есть достаточно много простых случаев, когда достаточно использовать placeholder auto c концептом. Например, для независимых ограничений типов аргументов функции:
void foo(const std::integral auto value);
Но для более сложных случаев предполагается использовать концепты и template совместно. И такое совместное использование оказывается гораздо более выразительным, но ключевое слово template в нём остаётся, а не убирается любой ценой, как в твоих примерах.
Или когда нужно вытащить параметры шаблона из типа аргумента.
Особенно, вариадики.
Например, идиома с индексами (да, понятно, что это лютый костыль, и неплохо бы в будущем от него избавиться)
Для не-вариадиков, положим, можно написать метафункции (всё равно это будет ад-хок)
template<class L, class R> struct cons {};
struct nil {};
template<class> struct cons_traits {
static constexpr bool value = false;
};
template<class L, class R> struct cons_traits<cons<L,R>> {
static constexpr bool value = true;
using left = L;
using right = R;
};
template<class C> static constexpr bool is_cons_v = cons_traits<C>::value;
template<class C> using left_t = cons_traits<C>::left;
template<class C> using right_t = cons_traits<C>::right;
// и уже по месту - никаких template...void do_something_with_list(auto l) {
using C = decltype((l));
if constexpr (is_cons_v<C>) {
using L = left_t<C>;
using R = right_t<C>;
.....
} else {
.....
}
}
Ну или же ограничить использование вариадиков, например, стандартным std::tuple и расшивать его с помощью std::apply.
Тут не понял. Кроме того авто результат должен всё вывести сам, особенно если функция написана в NVRO стиле. Впрочем в крайнем случае всегда есть синтаксис
f() -> return_type {};
К>Особенно, вариадики.
Вариадики — в точку. И воркараунд с tuple<> хорош. Я конечно не ставлю целей от template<> избавиться совсем, но если бы кто поставил себе задачу убить всё это легаси с бустом впридачу, упростив язык просто экспоненциально — помоему они были бы уже недалеко от цели.
К>Или когда нужно вытащить параметры шаблона из типа аргумента.
Тут тоже точно, сколько auto не пиши,
auto — конечно, мощная штука, но даже хорошими штуками злоупотреблять не стоит. Нужно понимать, что для того, чтоб вывести тип результата такой функции, комилятор должен сперва инстанцировать ее. На этом можно строить всякие интересные фишечки (например компайл-тайм счетчик). Но бывает и наоборот, когда необходимость инстанцирования оказывается совсем не к месту.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, reversecode, Вы писали:
R>есть еще не помню кейс где SFINAE не будет работать R>а концепт будет
А у SFINAE другая проблема: у него все перегрузки равноправны и нет способа сделать одну перегрузку "более специальной", чем другая. И иногда резолвить коллизии становится достаточно геморройно. С концептами же можно элегантно выстраивать иерархии типов и предоставлять перегрузки с приоритетами:
Здравствуйте, rg45, Вы писали:
R>auto — конечно, мощная штука, но даже хорошими штуками злоупотреблять не стоит. Нужно понимать, что для того, чтоб вывести тип результата такой функции, комилятор должен сперва инстанцировать ее. На этом можно строить всякие интересные фишечки (например компайл-тайм счетчик). Но бывает и наоборот, когда необходимость инстанцирования оказывается совсем не к месту.
Я специально оставил все стилистические вопросы за скобками, чтобы исследовать глубину и проработанность надстройки нового формата языка над стандартными шаблонами. Является ли она целостной и если нет, что осталось чтобы добить.
J>Тут не понял. Кроме того авто результат должен всё вывести сам, особенно если функция написана в NVRO стиле. Впрочем в крайнем случае всегда есть синтаксис
f() -> return_type {};
Ну-ну. И как ты укажешь, какой именно по номеру (или по типу) компонент варианта или кортежа должна вернуть функция?
Или же предлагаешь передавать фейковый аргумент, что-то вида
decltype(auto) get(auto&& var, auto discriminator)
requires (is_some_variant_v<decltype((var))> && is_type_discriminator_v<decltype(discriminator)>)
{
using T = decltype(discriminator)::type;
.....
}
// get(var, std::type_identity<int>{})
decltype(auto) get(auto&& tpl, auto discriminator)
requires (is_some_tuple_v<decltype((var))> && is_index_discriminator_v<decltype(discriminator)>)
{
static constexpr size_t N = decltype(discriminator)::value;
.....
}
// get(tpl, std::integral_constant<size_t, 1>{})
К>>Или когда нужно вытащить параметры шаблона из типа аргумента. J>Тут тоже точно, сколько auto не пиши,
f(std::vector<auto>& )
(пока) не напишешь.
А вот это как раз легко. Один день потерять, потом за пять минут долететь.
Здравствуйте, johny5, Вы писали:
J>Я специально оставил все стилистические вопросы за скобками, чтобы исследовать глубину и проработанность надстройки нового формата языка над стандартными шаблонами. Является ли она целостной и если нет, что осталось чтобы добить.
Это не вопрос стиля, это вопрос применимости данного подхода:
auto factorial(auto t)
{
return t ? t * factorial(t - 1) : decltype(t){1}; // error: use of 'auto factorial' before deduction of 'auto'
}
И это только иллюстрация, очень примитивная. В реальной жизни сценарии, когда необходимость выведения типа результата приводит к ощибкам, могут быть существенно сложнее. Ну и в целом, настойчивое стремление следовать именно такому стилю, лишь бы только избежать использования "старого синтаксиса", может выливаться в необоснованное усложнение задачи для компилятора.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>А у SFINAE другая проблема: у него все перегрузки равноправны и нет способа сделать одну перегрузку "более специальной", чем другая. И иногда резолвить коллизии становится достаточно геморройно. С концептами же можно элегантно выстраивать иерархии типов и предоставлять перегрузки с приоритетами:
А вот это уже какая-то чёртова магия.
Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.
Здравствуйте, Кодт, Вы писали:
К>А вот это уже какая-то чёртова магия. К>Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.
Почему бы и нет? Сабклассинг и сам по себе может иметь ценность. Это аналог того, как иногда используют наследование без какого-либо расширения функционала, только для того, чтобы создать новый UDT, перегрузить для него всякие операторы и заставить ADL заглятывать в нужное пространтво имен (а не в boost или std).
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
К>>А вот это уже какая-то чёртова магия. К>>Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.
R>Почему бы и нет? Сабклассинг и сам по себе может иметь ценность.
Субклассирование я знаю, зачем нужно И то, что субклассирование не обязательно выражается через наследование.
Просто в языке концептов субклассирование реализуется конъюнкцией.
Два концепта, оба тождественно true, но один из них более специализирован. Колдунство же!