template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 01.11.23 22:54
Оценка: 3 (1)
Привет.

У меня в голове болтается смутное ощущение что новые конструкции типа constexpr, f(auto arg), decltype, if contexpr и concept сводят на нет необходимость старого синтаксиса шаблонов через template<typename .. >, SFINAE и проче с ним, нет? Пока только рассматриваем функции — структуры, видимо, пока ещё требуют старый синтаксис.

Есть ли какие то примеры, которые ещё нельзя переписать целиком, используя новый синтаксис? Что вы думаете?
Re: template (синтаксис) больше не нужен?
От: reversecode google
Дата: 01.11.23 23:04
Оценка: +3
concept да сильно упрощает старый SFINAE и почти нивелирует его

constexpr вообще о другом
он работает уже после перегрузки

auto, decltype итд это вообще про третье
Re[2]: template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 01.11.23 23:37
Оценка:
Здравствуйте, reversecode, Вы писали:

Вот накидал пример с перегрузкой (оставим за скобками целесообразность кода, только синтаксис).

Было:
template<typename CONTAINER, typename OBJ_T>
typename CONTAINER::const_iterator  find(const CONTAINER& cont, const OBJ_T& obj)
{
    return std::find(cont.begin(), cont.end(), obj);
}

template<typename K, typename V, typename A, typename C, typename T>
typename std::map<K,V,A,C>::const_iterator  find(const std::map<K,V,A,C>& cont, const T& key)
{
    return cont.find(key);
}



Стало:
template<typename T, typename V>
concept HasFind = requires (T a, V b) { a.find(b); };

auto find(const auto& container, const auto& key) requires HasFind<decltype(container), decltype(key)>
{
    return container.find(key);
}

auto find(const auto& container, const auto& key) requires (!HasFind<decltype(container), decltype(key)>)
{
    return std::find(container.begin(), container.end(), key);
}


template<typename .. исчезли. Правда они появились у concept, но это новый, мимикрирующий синтаксис.

Playground
Отредактировано 01.11.2023 23:52 johny5 . Предыдущая версия .
Re[3]: template (синтаксис) больше не нужен?
От: reversecode google
Дата: 02.11.23 00:04
Оценка:
и ?
одна из идей концептов адекватный выхлоп об ошибке
в SFINAE вы получите килотонны выхлопа о том что то там где не понятно что произошло

есть еще не помню кейс где SFINAE не будет работать
а концепт будет
Re[2]: template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 02.11.23 00:15
Оценка:
if constexpr пример.

Было:

HAS_FUNC_CLASS(elem_dif_save);
template<bool b, typename T>
struct elem_dif_save_helper
{
    static void save(const T& data, const T& old_data, StreamWriter& sw)
    {
        return (data.T::elem_dif_save)(old_data, sw);
    }
};

template<typename T>
struct elem_dif_save_helper<false, T>
{
    static void save(const T& data, const T&  , StreamWriter& sw)
    {
        //    fallback
        return (data.T::elem_save)(sw);
    }
};


template<class T> inline void elem_dif_save(const T& val, const T& old_val, StreamWriter& sw)
{
    return elem_dif_save_helper< HAS_FUNC_VALUE(elem_dif_save, T), T >::save(val, old_val, sw); 
}



Стало
template<typename T>
concept has_elem_dif_save = std::is_member_function_pointer_v<decltype(&T::elem_dif_save)>;

inline void elem_dif_save(const auto& val, const auto& old_val, StreamWriter& sw) requires std::same_as<decltype(val), decltype(old_val)>
{
    using T = std::decay_t<decltype(val)>;
    
    if constexpr (has_elem_dif_save<T>)
        return (val.T::elem_dif_save)(old_val, sw);
    else 
        return (val.T::elem_save)(sw);    //    fallback
}


Playground
Re[3]: template (синтаксис) больше не нужен?
От: Chorkov Россия  
Дата: 03.11.23 07:46
Оценка:
Здравствуйте, johny5, Вы писали:

...
J>>Есть ли какие то примеры, которые ещё нельзя переписать целиком, используя новый синтаксис? Что вы думаете?
...

J>
J>template<typename T>
J>concept has_elem_dif_save = std::is_member_function_pointer_v<decltype(&T::elem_dif_save)>;

J>inline void elem_dif_save(const auto& val, const auto& old_val, StreamWriter& sw) requires std::same_as<decltype(val), decltype(old_val)>
J>{
J>    using T = std::decay_t<decltype(val)>;
    
J>    if constexpr (has_elem_dif_save<T>)
J>        return (val.T::elem_dif_save)(old_val, sw);
J>    else 
J>        return (val.T::elem_save)(sw);    //    fallback
J>}

J>


Это как раз пример, когда template<> читабельнее чем auto (ИМХО):

J>
template<typename T>
concept has_elem_dif_save = std::is_member_function_pointer_v<decltype(&T::elem_dif_save)>;

template<typename T>
auto elem_dif_save(const T& val, const T& old_val, StreamWriter& sw) 
{
    if constexpr (has_elem_dif_save<T>)
        return val.elem_dif_save(old_val, sw);
    else 
        return val.elem_save(sw);    //    fallback
}
J>


Кроме того, template<> вариант позволяет явно указать типы в точке вызова, когда компилятор не может сообразить сам, например, если два аргумента имеют общую базу, и надо привести к ней.
Re[3]: template (синтаксис) больше не нужен?
От: watchmaker  
Дата: 04.11.23 04:26
Оценка: 9 (2)
Здравствуйте, johny5, Вы писали:

J>Вот накидал пример с перегрузкой (оставим за скобками целесообразность кода, только синтаксис).


J>Было: ...

J>Стало: ...

Получились скорее демотивирующие примеры, которые показывают как делать не нужно. И выглядят они неидеоматично.
То есть код работать будет, но это не тот способ использования концептов, который задумывался при их добавлении в язык.

J>requires (!HasFind<decltype(container), decltype(key)>)


Это при SFINAE нужно было следить, чтобы условия под всякими std::enable_if были взаимоисключающими, чтобы не было неоднозначности при нескольких подходящих альтернативах. А ограничения, заданные через requires, уже имеют (частичный) порядок.
То есть не нужно писать
auto find(const auto& container, const auto& key) requires (!HasFind<decltype(container), decltype(key)>) {...}
, а достаточно просто
auto find(const auto& container, const auto& key) {...}

Компилятор и так обязан выбрать первую перегрузку (через метод find) в случае, когда обе они подходят, потому что она более ограниченная/специфичная.

Заодно это убирает и момент удивления при чтении кода: "зачем в нём проверяется что-то про наличие метода, если в теле функции ничего такого не используется?". Потому что это было техническое ограничение при реализации через шаблоны. А в концептах почти всегда стоит проверять только те требования к типу, которые действительно нужны.


J>auto find(const auto& container, const auto& key) requires HasFind<decltype(container), decltype(key)>


Во-первых, такое сложно читать — убирание template не упрощает чтение кода.

Во-вторых, в концептах же специально сделана поддержка простой записи ограничений на типы: вместо const auto& container достаточно написать const HasFind<KeyType> auto& container и убрать ручные проверки и секцию require полностью. Компилятор сам подставит тип контейнера в концепт HasFind нужным параметром.

Опять же, в зависимости от сложности выражения это можно использовать как совместно с placeholder'ом auto (как выше), так и внутри template.
template <class KeyType, HasFind<KeyType> ContainerType>
auto find(const ContainerType& container, const KeyType& key) {
    ...
}

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


J> concept сводят на нет необходимость старого синтаксиса шаблонов через template<typename .. >

Не совсем.
Есть достаточно много простых случаев, когда достаточно использовать placeholder auto c концептом. Например, для независимых ограничений типов аргументов функции:
void foo(const std::integral auto value);

Но для более сложных случаев предполагается использовать концепты и template совместно. И такое совместное использование оказывается гораздо более выразительным, но ключевое слово template в нём остаётся, а не убирается любой ценой, как в твоих примерах.
Отредактировано 05.11.2023 23:11 watchmaker . Предыдущая версия . Еще …
Отредактировано 04.11.2023 4:31 watchmaker . Предыдущая версия .
Re: template (синтаксис) больше не нужен?
От: Кодт Россия  
Дата: 04.11.23 22:02
Оценка: 4 (1)
Здравствуйте, johny5, Вы писали:

J>Есть ли какие то примеры, которые ещё нельзя переписать целиком, используя новый синтаксис? Что вы думаете?


Очевидно, когда параметр шаблона не выводится из аргументов.
Например, когда он относится к результату.
template<class T> T& get(auto& var) requires (is_some_variant_v<decltype((var))>) { ..... }

template<size_t N> auto& get(auto& tpl) requires (is_some_tuple_v<decltype((tpl))>) { ..... }


Или когда нужно вытащить параметры шаблона из типа аргумента.
Особенно, вариадики.
Например, идиома с индексами (да, понятно, что это лютый костыль, и неплохо бы в будущем от него избавиться)
template<size_t... Ns> void do_something_with_all_elements(auto& tpl, std::index_sequence<Ns...>) {
  ( do_something(std::get<Ns>(tpl)) , ... );
}

void do_something_with_all_elements(auto& tpl) {
  do_something_with_all_elements(tpl, std::make_index_sequence<std::tuple_size<decltype((tpl))>());
}


Для не-вариадиков, положим, можно написать метафункции (всё равно это будет ад-хок)
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.
Перекуём баги на фичи!
Отредактировано 04.11.2023 22:05 Кодт . Предыдущая версия .
Re[2]: template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 04.11.23 23:41
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Очевидно, когда параметр шаблона не выводится из аргументов.

К>Например, когда он относится к результату.
К>
К>template<class T> T& get(auto& var) requires (is_some_variant_v<decltype((var))>) { ..... }

К>template<size_t N> auto& get(auto& tpl) requires (is_some_tuple_v<decltype((tpl))>) { ..... }
К>


Тут не понял. Кроме того авто результат должен всё вывести сам, особенно если функция написана в NVRO стиле. Впрочем в крайнем случае всегда есть синтаксис
f() -> return_type {};


К>Особенно, вариадики.

Вариадики — в точку. И воркараунд с tuple<> хорош. Я конечно не ставлю целей от template<> избавиться совсем, но если бы кто поставил себе задачу убить всё это легаси с бустом впридачу, упростив язык просто экспоненциально — помоему они были бы уже недалеко от цели.

К>Или когда нужно вытащить параметры шаблона из типа аргумента.

Тут тоже точно, сколько auto не пиши,
f(std::vector<auto>& )
(пока) не напишешь.
Re[3]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 05.11.23 21:07
Оценка:
Здравствуйте, johny5, Вы писали:

J>
J>auto find(const auto& container, const auto& key) requires (!HasFind<decltype(container), decltype(key)>)
J>{
J>    return std::find(container.begin(), container.end(), key);
J>}
J>


auto — конечно, мощная штука, но даже хорошими штуками злоупотреблять не стоит. Нужно понимать, что для того, чтоб вывести тип результата такой функции, комилятор должен сперва инстанцировать ее. На этом можно строить всякие интересные фишечки (например компайл-тайм счетчик). Но бывает и наоборот, когда необходимость инстанцирования оказывается совсем не к месту.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 05.11.2023 22:23 rg45 . Предыдущая версия . Еще …
Отредактировано 05.11.2023 21:22 rg45 . Предыдущая версия .
Re[4]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 05.11.23 22:33
Оценка:
Здравствуйте, reversecode, Вы писали:

R>есть еще не помню кейс где SFINAE не будет работать

R>а концепт будет

Могу привести обратный пример, когда концепт не работает, а SFINAE работает.

Вот: http://coliru.stacked-crooked.com/a/faf667091accb800

P.S. На msvc работает и SFINAE, и концепт. И, по идее, оно и должно работать, непонятно, чего gcc капризничает. Не исключено, что там просто бага.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 06.11.2023 20:44 rg45 . Предыдущая версия . Еще …
Отредактировано 05.11.2023 23:03 rg45 . Предыдущая версия .
Re[4]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 05.11.23 23:27
Оценка: +1
Здравствуйте, reversecode, Вы писали:

R>есть еще не помню кейс где SFINAE не будет работать

R>а концепт будет

А у SFINAE другая проблема: у него все перегрузки равноправны и нет способа сделать одну перегрузку "более специальной", чем другая. И иногда резолвить коллизии становится достаточно геморройно. С концептами же можно элегантно выстраивать иерархии типов и предоставлять перегрузки с приоритетами:

http://coliru.stacked-crooked.com/a/068edfc7e85e1e14

#include <iostream>
#include <type_traits>
#include <concepts>

template <typename T>
concept Number = std::is_arithmetic_v<T>;

template <typename T>
concept RealNumber = Number<T> && std::is_floating_point_v<T>;

void foo(Number auto t) { std::cout << "Number: " << t << std::endl; }

void foo(RealNumber auto t) { std::cout << "Real Number: " << t << std::endl; }

void bar(Number auto t) { std::cout << "This is a Number: " << t << std::endl; }

int main()
{
   foo(42); // Number: 42
   foo(3.14); // Real Number: 3.14

   bar(42); // This is a Number: 42
   bar(3.14); // This is a Number: 3.14
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 05.11.2023 23:52 rg45 . Предыдущая версия . Еще …
Отредактировано 05.11.2023 23:50 rg45 . Предыдущая версия .
Отредактировано 05.11.2023 23:34 rg45 . Предыдущая версия .
Re[4]: template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 06.11.23 01:30
Оценка:
Здравствуйте, rg45, Вы писали:

R>auto — конечно, мощная штука, но даже хорошими штуками злоупотреблять не стоит. Нужно понимать, что для того, чтоб вывести тип результата такой функции, комилятор должен сперва инстанцировать ее. На этом можно строить всякие интересные фишечки (например компайл-тайм счетчик). Но бывает и наоборот, когда необходимость инстанцирования оказывается совсем не к месту.


Я специально оставил все стилистические вопросы за скобками, чтобы исследовать глубину и проработанность надстройки нового формата языка над стандартными шаблонами. Является ли она целостной и если нет, что осталось чтобы добить.
Re[3]: template (синтаксис) больше не нужен?
От: Кодт Россия  
Дата: 06.11.23 17:10
Оценка: 2 (1)
Здравствуйте, johny5, Вы писали:

К>>Очевидно, когда параметр шаблона не выводится из аргументов.

К>>Например, когда он относится к результату.
К>>
К>>template<class T> T& get(auto& var) requires (is_some_variant_v<decltype((var))>) { ..... }

К>>template<size_t N> auto& get(auto& tpl) requires (is_some_tuple_v<decltype((tpl))>) { ..... }
К>>


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>& )
(пока) не напишешь.


А вот это как раз легко. Один день потерять, потом за пять минут долететь.
template<class T> struct is_std_vector : std::false_type {};
template<class T> struct is_std_vector<std::vector<T>> : std::true_type {};

template<class T> concept StdVector = is_std_vector<T>::value;

void f(StdVector auto& v);
Перекуём баги на фичи!
Re[4]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 06.11.23 19:29
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А вот это как раз легко. Один день потерять, потом за пять минут долететь.

К>
К>template<class T> struct is_std_vector : std::false_type {};
К>template<class T> struct is_std_vector<std::vector<T>> : std::true_type {};

К>template<class T> concept StdVector = is_std_vector<T>::value;

К>void f(StdVector auto& v);
К>


А примерчик-то прямо в тему: для того, чтобы объявить концепт, сперва пришлось прибегнуть к SFINAE магии

P.S. А, пардон, здесь обычная специализация, а не SFINAE. Но все равно это противоречит тезису, что шаблоны старого стиля больше не нужны.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 06.11.2023 19:31 rg45 . Предыдущая версия .
Re[4]: template (синтаксис) больше не нужен?
От: johny5 Новая Зеландия
Дата: 06.11.23 19:47
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ну-ну. И как ты укажешь, какой именно по номеру (или по типу) компонент варианта или кортежа должна вернуть функция?


А, тип или compile-time константа как аргумент функции? Теперь понял. Хороший пример.
Re[5]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 07.11.23 09:26
Оценка:
Здравствуйте, johny5, Вы писали:

J>Я специально оставил все стилистические вопросы за скобками, чтобы исследовать глубину и проработанность надстройки нового формата языка над стандартными шаблонами. Является ли она целостной и если нет, что осталось чтобы добить.


Это не вопрос стиля, это вопрос применимости данного подхода:

http://coliru.stacked-crooked.com/a/535db5b8699e7bf6

auto factorial(auto t)
{
   return t ? t * factorial(t - 1) : decltype(t){1}; // error: use of 'auto factorial' before deduction of 'auto'
}


И это только иллюстрация, очень примитивная. В реальной жизни сценарии, когда необходимость выведения типа результата приводит к ощибкам, могут быть существенно сложнее. Ну и в целом, настойчивое стремление следовать именно такому стилю, лишь бы только избежать использования "старого синтаксиса", может выливаться в необоснованное усложнение задачи для компилятора.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 07.11.2023 9:47 rg45 . Предыдущая версия . Еще …
Отредактировано 07.11.2023 9:39 rg45 . Предыдущая версия .
Отредактировано 07.11.2023 9:39 rg45 . Предыдущая версия .
Отредактировано 07.11.2023 9:35 rg45 . Предыдущая версия .
Отредактировано 07.11.2023 9:31 rg45 . Предыдущая версия .
Re[5]: template (синтаксис) больше не нужен?
От: Кодт Россия  
Дата: 07.11.23 15:09
Оценка: 1 (1) +1 :)
Здравствуйте, rg45, Вы писали:

R>А у SFINAE другая проблема: у него все перегрузки равноправны и нет способа сделать одну перегрузку "более специальной", чем другая. И иногда резолвить коллизии становится достаточно геморройно. С концептами же можно элегантно выстраивать иерархии типов и предоставлять перегрузки с приоритетами:


А вот это уже какая-то чёртова магия.
Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.

http://coliru.stacked-crooked.com/a/e4a93aa34fae33b8
template <typename T>
concept Number = true;

template <typename T>
concept RealNumber = Number<T> && true;

template <typename T>
concept UnrealNumber = Number<T> && true;

template <typename T>
concept SuperNumber = RealNumber<T> && true;

void foo(Number auto t) { std::cout << "Number: " << t << std::endl; }
void foo(RealNumber auto t) { std::cout << "Real Number: " << t << std::endl; }
//void foo(UnrealNumber auto t) { std::cout << "Unreal Number: " << t << std::endl; }  // будет неоднозначность
void foo(SuperNumber auto t) { std::cout << "Super Number: " << t << std::endl; }

int main()
{
    foo(123);
}
Перекуём баги на фичи!
Re[6]: template (синтаксис) больше не нужен?
От: rg45 СССР  
Дата: 07.11.23 15:24
Оценка: +1
Здравствуйте, Кодт, Вы писали:

К>А вот это уже какая-то чёртова магия.

К>Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.

Почему бы и нет? Сабклассинг и сам по себе может иметь ценность. Это аналог того, как иногда используют наследование без какого-либо расширения функционала, только для того, чтобы создать новый UDT, перегрузить для него всякие операторы и заставить ADL заглятывать в нужное пространтво имен (а не в boost или std).
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 07.11.2023 15:32 rg45 . Предыдущая версия . Еще …
Отредактировано 07.11.2023 15:30 rg45 . Предыдущая версия .
Отредактировано 07.11.2023 15:27 rg45 . Предыдущая версия .
Re[7]: template (синтаксис) больше не нужен?
От: Кодт Россия  
Дата: 07.11.23 23:35
Оценка:
Здравствуйте, rg45, Вы писали:

К>>А вот это уже какая-то чёртова магия.

К>>Когда булево выражение у концепта трактуется не просто как булево выражение, а как субклассирование.

R>Почему бы и нет? Сабклассинг и сам по себе может иметь ценность.


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

Просто в языке концептов субклассирование реализуется конъюнкцией.
Два концепта, оба тождественно true, но один из них более специализирован. Колдунство же!
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.