Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 25.10.22 21:45
Оценка:
Возможен ли сабж в принципе? Что-то туплю. Хочется примерно следующего:
#include <iostream>

template <typename type_t>
struct object
{
    void print() { std::cout << "base template"; }
};

template <>
struct object<int>
{
    void print() { std::cout << "specialized template"; }
};

int main()
{
    object<int> obj; // force base template here
    obj.print();

    return 0;
}
Временно отменить специализацию и создать базовый вариант шаблона для int-а, что бы код выдал: "base template".

Задачка неожиданно возникла во время игр с std::format. Ребята в комитете придумали, что кастомные форматеры, это специализации классов. Ну сделал я специализацию для своего класса, который тоже является специализацией библиотечного шаблона. А как мне теперь вызвать реализацию по умолчанию для базового шаблона ? Или я что-то не то делаю
Re: Cоздание базового шаблона минуя специализацию
От: _niko_ Россия  
Дата: 26.10.22 06:36
Оценка:
Здравствуйте, Videoman, Вы писали:

V> Ну сделал я специализацию для своего класса

Все, тут твоя специализация становится тем единственным классом с которым ты работаешь, никакого понятия(знания) о "базовой" реализации нет и быть не должно )
Если есть какой то код находящийся в этой "базовой" реализации который хочется заюзать, значит вытаскивай его на уровень выше:

struct base_api
{
  void print() { std::cout << "base template"; }
};

template <typename type_t>
struct object : base_api
{
  ...
};

template <>
struct object<int> : base_api
{
    void print() { std::cout << "specialized template"; }
};

int main()
{
    object<int> obj;
    obj.base_api::print();

    return 0;
}


Но все же
obj.base_api::print();
— это говорит о том что тут что то пошло не так и лучше бы перепроектировать
Re[2]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 07:23
Оценка:
Здравствуйте, _niko_, Вы писали:

__>...


__>Но все же
obj.base_api::print();
— это говорит о том что тут что то пошло не так и лучше бы перепроектировать




Это же не я, это стандартная библиотека такую свинью подложила, std. Вот эти вот классы, они в std определены и специализировать мне их предлагают по стандарту. А я говорил, что люди принимающие стандарты, по ходу вообще не думают, последнее время.
Конструктивная критика: почему они сделали классы, почему не функции, как это сделал я в свое время в своей библиотеке форматирования. Если сделать форматер для всех наследников какого-то класса еще можно, с помощью SFINAE, то ADL для специализаций классов не работает и нет возможности определять форматеры в своём пространстве имен, оставляя возможность вызывать базовые из std.
Re: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 26.10.22 11:08
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Возможен ли сабж в принципе? Что-то туплю. Хочется примерно следующего:[ccode]

V>Временно отменить специализацию и создать базовый вариант шаблона для int-а, что бы код выдал: "base template".

Вот прямо в такой формулировке, думаю, что невозможен — потому что в таком случае возникает две различные версии одной и той же специализации, а это нарушение ODR. Нужно думать, какие тут можно костыли прикрутить.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 11:54
Оценка:
Здравствуйте, rg45, Вы писали:

R>Вот прямо в такой формулировке, думаю, что невозможен — потому что в таком случае возникает две различные версии одной и той же специализации, а это нарушение ODR. Нужно думать, какие тут можно костыли прикрутить.


Наверное вопрос в этом, как выкрутится теперь. Ситуация простая. У std::formatter есть специализация для вывода std::chrono::duration<>. У меня в библиотеках везде используется своя специализация времени:
using reftime_t = std::chrono::duration<int64, std:ratio<1, 10000000>>;
Стандартные возможности форматирования огромны, но мне хочется их расширить для своего типа времени и переиспользовать. Таким образом объявляя
template <>
struct std::formatter<reftime_t> {
    // ...
};
я автоматически лишаюсь возможности вызвать стандартную реализацию и вынужден повторять всю логику заново. Это не дело, я считаю
Re[3]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 26.10.22 12:18
Оценка: +1
Здравствуйте, Videoman, Вы писали:

V>Наверное вопрос в этом, как выкрутится теперь. Ситуация простая. У std::formatter есть специализация для вывода std::chrono::duration<>. У меня в библиотеках везде используется своя специализация времени:
V>using reftime_t = std::chrono::duration<int64, std:ratio<1, 10000000>>;
V>


Поскольку в C++ нет strong typedef, то компилятор C++ не может отличить ваш reftime_t от каких-либо других алиасов для std::chrono::duration.

ИМХО, тут напрашивается одно из двух:

— либо вы делаете свой reftime_t таки strong typedef-ом для std::chrono::duration, чтобы это был именно что другой тип на уровне C++. И тогда делаете для своего типа нужную вам специализацию std::formatter (наследуясь от std::formatter<std::chrono::duration<...>>);

— либо вы делаете обертку для своего reftime_t:

struct reftime_for_std_format_t {
  reftime_t val_;
};

[[nodiscard]]
inline reftime_for_std_format_t wrap(reftime_t v) { return { v }; }


Затем пишете для reftime_for_std_format_t специализацию std::formatter. И отображаете свои значения reftime_t посредством wrap:
std::format("Duration is {}", wrap(your_reftime_value));


И тот, и другой способы геморройные. Разве что для первого способа можно задействовать какую-то готовую библиотеку с реализацией strong typedef.
Re[3]: Cоздание базового шаблона минуя специализацию
От: σ  
Дата: 26.10.22 12:38
Оценка:
V>Это же не я, это стандартная библиотека такую свинью подложила, std. Вот эти вот классы, они в std определены и специализировать мне их предлагают по стандарту.

Да вроде в std нельзя добавлять специализаций, не зависящих от program-defined type (https://timsong-cpp.github.io/cppwp/n4868/namespace.std#2)
Re[4]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 15:44
Оценка:
Здравствуйте, σ, Вы писали:

σ>Да вроде в std нельзя добавлять специализаций, не зависящих от program-defined type (https://timsong-cpp.github.io/cppwp/n4868/namespace.std#2)


Специализации определённых в std шаблонов добавлять можно. Нельзя добавлять свои новые имена.

Если явно не запрещено, программа может добавить специализацию шаблона для любого шаблона класса стандартной библиотеки в пространство имен std при условии, что (а) добавленное объявление зависит по крайней мере от одного определенного в программе типа и (б) специализация соответствует требованиям стандартной библиотеки для оригинальный шаблон .

Re[4]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 15:51
Оценка:
Здравствуйте, so5team, Вы писали:

S>И тот, и другой способы геморройные. Разве что для первого способа можно задействовать какую-то готовую библиотеку с реализацией strong typedef.


Всё это очевидно. Тут больше вопросы к тем, кто принимает какое в std.
Свою проблему я решил, т.к. оказывается, что всю работу делает специализация formatter<std::time_t> и все вызовы в итоге приводят туда, а любой chrono тип приводится к time_t. Но это просто везение. А что делать, если это не так, по прежнему остается загадкой. С таким дизайном стандартной библиотеки, будем надеяться, что стандартных форматеров хватит всем, на все случаи жизни.
Отредактировано 26.10.2022 15:52 Videoman . Предыдущая версия .
Re[5]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 26.10.22 16:05
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Тут больше вопросы к тем, кто принимает какое в std.


Если вы хоте вести речь в таком тоне, то позвольте встречный вопрос: а что лично вы сделали, чтобы в стандарт попало что-то более вменяемое?
Re[6]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 17:17
Оценка: +2
Здравствуйте, so5team, Вы писали:

S>Если вы хоте вести речь в таком тоне, то позвольте встречный вопрос: а что лично вы сделали, чтобы в стандарт попало что-то более вменяемое?


Ответ на это вопрос выходит за рамки топика. Не знаю что вам не нравится в тоне. Конструктивную критику я уже высказал: т.к. у меня была реализация точно такого же функционала с точно таким же синтаксисом, но она была построена на функциях, т.к. к ним применим ADL, а с ним возможна кастомизация форматирования для каждого конкретного неймспейса. Давайте лучше заниматься каждый своим делом, но желательно так, что бы те кто пользуются этими результатами не страдали, а были довольны тем, как замечательно мы с вами все за них продумали.
Re[5]: Cоздание базового шаблона минуя специализацию
От: σ  
Дата: 26.10.22 17:22
Оценка:
σ>>Да вроде в std нельзя добавлять специализаций, не зависящих от program-defined type (https://timsong-cpp.github.io/cppwp/n4868/namespace.std#2)

V>Специализации определённых в std шаблонов добавлять можно. Нельзя добавлять свои новые имена.

V>Если явно не запрещено, программа может добавить специализацию шаблона для любого шаблона класса стандартной библиотеки в пространство имен std при условии, что (а) добавленное объявление зависит по крайней мере от одного определенного в программе типа и (б) специализация соответствует требованиям стандартной библиотеки для оригинальный шаблон .

И шо, std::chrono::duration<int64, std:ratio<1, 10000000>> удовлетворяет условию (а)?
Re[6]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 17:39
Оценка:
Здравствуйте, σ, Вы писали:

σ>И шо, std::chrono::duration<int64, std:ratio<1, 10000000>> удовлетворяет условию (а)?


Да согласен, слишком упростил пример. У меня на самом деле перекрывается специализация std::chrono::time_point<reference_clock>, где reference_clock уже точно мой класс.
Re[7]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 26.10.22 19:05
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Конструктивную критику я уже высказал: т.к. у меня была реализация точно такого же функционала с точно таким же синтаксисом, но она была построена на функциях, т.к. к ним применим ADL, а с ним возможна кастомизация форматирования для каждого конкретного неймспейса.


Поддерживаю всеми руками.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Cоздание базового шаблона минуя специализацию
От: σ  
Дата: 26.10.22 20:06
Оценка:
V>Конструктивная критика: почему они сделали классы, почему не функции, как это сделал я в свое время в своей библиотеке форматирования. Если сделать форматер для всех наследников какого-то класса еще можно, с помощью SFINAE, то ADL для специализаций классов не работает и нет возможности определять форматеры в своём пространстве имен, оставляя возможность вызывать базовые из std.

Да вроде ответ гуглится.
Re[4]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 26.10.22 20:49
Оценка:
Здравствуйте, σ, Вы писали:

σ>Да вроде ответ гуглится.


Может и гуглится, но мне вот не попадалось. Как с таким подходом как в std сделать свою кастомизацию форматирования int, например?
Re[5]: Cоздание базового шаблона минуя специализацию
От: σ  
Дата: 26.10.22 21:52
Оценка: +1
V>Как с таким подходом как в std сделать свою кастомизацию форматирования int, например?

А как через ADL сделать?
Re[7]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 04:53
Оценка: +1
Здравствуйте, Videoman, Вы писали:

S>>Если вы хоте вести речь в таком тоне, то позвольте встречный вопрос: а что лично вы сделали, чтобы в стандарт попало что-то более вменяемое?


V>Ответ на это вопрос выходит за рамки топика.


А вот это все еще в рамках топика?

Это же не я, это стандартная библиотека такую свинью подложила, std. Вот эти вот классы, они в std определены и специализировать мне их предлагают по стандарту. А я говорил, что люди принимающие стандарты, по ходу вообще не думают, последнее время.


V>Не знаю что вам не нравится в тоне.


Поведение в стиле "вот у меня все было по уму, а в std включили хз что"

V>Конструктивную критику я уже высказал: т.к. у меня была реализация точно такого же функционала с точно таким же синтаксисом, но она была построена на функциях, т.к. к ним применим ADL, а с ним возможна кастомизация форматирования для каждого конкретного неймспейса.


И где здесь критика? Я не увидел ни ссылки на исходники вашей библиотеки, в которой можно было бы посмотреть на альтернативный подход. Ни описания деталей этого подхода здесь. Более того, здесь видно как вы сами путаетесь в показаниях, т.к. у вас сперва reftime_t -- это синоним для стандартного std::chrono::duration, а потом оказывается, что все несколько не так.

Конструктивная критика должна, как минимум, идти в https://stdcpp.ru/

Здесь же наблюдается какое-то брюзжание из категории "меня не спросили".

std::format взялся не на пустом месте. Много лет в OpenSource была fmtlib, автор которой потратил туеву хучу времени и сил на то, чтобы выложить это в OpenSource, снабдить документацией, среагировать на огромное количество issues, принять кучу pull-request-ов, выслушать в свой адрес всякое-разное. А потом еще и принять участие в формировании предложения в стандарт, и пройти вместе с другими соавторами через процесс включения в стандарт.

Причем процесс включения в стандарт был открытым, высказывать свои замечания/предложения к предложению можно было еще до того, как std::format стандартизировали.

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

Так что если кто-то вопрошает, а почему в стандарте вот такое, когда можно было бы вот так, то ответ очень простой: потому что тот, кто знает как "вот так" не приложил никаких усилий к тому, чтобы его знание тем или иным образом было доведено до комитета.

И тут не суть важно, была ли у вас такая цель и желание, или не было. Ситуация с тем, что попадает в стандарт C++, проста: кто приложил усилия, тот и добился (или нет). И так оно уже лет 30, можно было бы и привыкнуть.
Re[8]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 09:09
Оценка:
Здравствуйте, so5team, Вы писали:

S>Конструктивная критика должна, как минимум, идти в https://stdcpp.ru/

Если все программисты будут сидеть на stdcpp.ru, то кто и когда будет делать всё остальное. Искренне считаю, что то, что вы предлагаете, постоянно сидеть на форуме С++ и мониторить все предложения, анализировать их и проверять — не возможно на практике.

S>Здесь же наблюдается какое-то брюзжание из категории "меня не спросили".

Вот именно брюзжание — у вас. Я просто задал интересующий меня вопрос, для того что бы удостовериться что я не пропускаю очевидное решение столкнувшись с проблемой. Дальше я просто удивился, что нужной, как мне кажется, не только лично мне, кастомизации не предусмотрено ни в каком виде — всё. Ну посетовал немного, это запрещено на RSDN?

S>std::format взялся не на пустом месте. Много лет в OpenSource была fmtlib, автор которой потратил туеву хучу времени и сил на то, чтобы выложить это в OpenSource, снабдить документацией, среагировать на огромное количество issues, принять кучу pull-request-ов, выслушать в свой адрес всякое-разное. А потом еще и принять участие в формировании предложения в стандарт, и пройти вместе с другими соавторами через процесс включения в стандарт.

Знаем знаем как всё там не на пустом месте. Ребята из Microsoft запилили библиотеку для своих личных нужд и пропихнули её в стандарт, т.к. могли.

S>Причем процесс включения в стандарт был открытым, высказывать свои замечания/предложения к предложению можно было еще до того, как std::format стандартизировали.

Теоретически. Законы вот по такому же принципу воде принимаются, однако на практике на что-то влиять практически не возможно. Приходится довольствоваться тем что есть.

S>Я это все к тому, что вмешаться в процесс можно было раньше. Если не на стадии развития fmtlib, так на стадии прохождения пропозала через комитет.

Я прекрасно понимаю что такое решение получено в процессе консенсуса. Оно вообще по результату может полностью не устраивать все стороны которые его, вроде бы, принимали. Но зачем так яростно его защищать, даже если там есть очевидные недостатки
Re[6]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 09:14
Оценка:
Здравствуйте, σ, Вы писали:

V>>Как с таким подходом как в std сделать свою кастомизацию форматирования int, например?


σ>А как через ADL сделать?


Как такое сделать дня базовых типов я не знаю. А можно кто-то в комитете продумает за меня ?!. В своём решении я, во всяком случае, могу это сделать. Для остальных классов можно хотя бы дать возможность не подключать заголовки стандартных форматеров, а использовать свои.
Отредактировано 27.10.2022 9:16 Videoman . Предыдущая версия .
Re[9]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 09:19
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Конструктивная критика должна, как минимум, идти в https://stdcpp.ru/

V>Искренне считаю, что то, что вы предлагаете, постоянно сидеть на форуме С++ и мониторить все предложения, анализировать их и проверять — не возможно на практике.

Где я такое предлагал?

V>Ну посетовал немного, это запрещено на RSDN?


Вы назвали свое сетование конструктивной критикой.

На практике же лично я не понял ни что вас не устраивало (т.е. в чем именно у вас была проблема), ни как вы смогли решить свою проблему, ни как эта проблема решалась в вашей библиотеке (т.е. не увидел никаких реальных альтернатив подходу std::format/fmt::format).

Так что конструктива в ваших сообщениях в этой теме лично я не вижу. Если что-то таковое есть, то могли бы вы показать пальцем?

S>>std::format взялся не на пустом месте. Много лет в OpenSource была fmtlib, автор которой потратил туеву хучу времени и сил на то, чтобы выложить это в OpenSource, снабдить документацией, среагировать на огромное количество issues, принять кучу pull-request-ов, выслушать в свой адрес всякое-разное. А потом еще и принять участие в формировании предложения в стандарт, и пройти вместе с другими соавторами через процесс включения в стандарт.

V>Знаем знаем как всё там не на пустом месте. Ребята из Microsoft запилили библиотеку для своих личных нужд и пропихнули её в стандарт, т.к. могли.

Мы все еще говорим про fmtlib и std::format?

V>Но зачем так яростно его защищать, даже если там есть очевидные недостатки


Можно хотя бы одну цитату из моих слов, из которой бы следовало, что я защищаю std::format (или какое-либо из технических решений, примененных в std::format)?
Re[7]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 09:32
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Для остальных классов можно хотя бы дать возможность не подключать заголовки стандартных форматеров, а использовать свои.


Простите, а это как?

Вот, допустим, есть специализация std::formatter<std::time_t>. Вы не подключаете заголовок, в котором эта специализация определена, а делаете собственную специализацию std::formatter<std::time_t> в своем .cpp-файле, но в пространстве имен std.

Я правильно вас понял?
Re[8]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 11:35
Оценка:
Здравствуйте, so5team, Вы писали:

S>Вот, допустим, есть специализация std::formatter<std::time_t>. Вы не подключаете заголовок, в котором эта специализация определена, а делаете собственную специализацию std::formatter<std::time_t> в своем .cpp-файле, но в пространстве имен std.


S>Я правильно вас понял?


Нет. Если ввести какой-нибудь формальный шаблонный параметр, который будет контролировать область поиска, то в случае реализация форматеров на функциях, ADL не увидит их и начнет искать в заданном вами найспейсе.
Re[9]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 11:39
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Нет. Если ввести какой-нибудь формальный шаблонный параметр, который будет контролировать область поиска, то в случае реализация форматеров на функциях, ADL не увидит их и начнет искать в заданном вами найспейсе.


Очень не хватает примеров. Мне до сих пор не понятно, что значит "реализация форматеров на функциях".
Re[10]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 11:55
Оценка:
Здравствуйте, so5team, Вы писали:

S>Очень не хватает примеров. Мне до сих пор не понятно, что значит "реализация форматеров на функциях".


Посмотрите доклад Полухина
  C++ трюки из Такси
C++ трюки из Такси

У него там хорошо объясняется подход.
Re[11]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 12:00
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Очень не хватает примеров. Мне до сих пор не понятно, что значит "реализация форматеров на функциях".


V>Посмотрите доклад Полухина


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

Ну OK, пересмотрю доклад и буду гадать, а как Videoman это все сам понял.

Конструктивно, однако.
Re[12]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 12:36
Оценка:
Здравствуйте, so5team, Вы писали:

S>И библиотека у вас секретная, даже фрагмента оттуда увидеть нельзя.

S>И вы настолько заняты, что минимальный пример продемонстрировать не можете.

S>Ну OK, пересмотрю доклад и буду гадать, а как Videoman это все сам понял.


S>Конструктивно, однако.


Ну чо вы такой душный, а?! Ну никакого позитива. Отдельной библиотеки нет, есть огромная библиотека-платформа для всего. Работает под Linux и Windows. Отодрать от туда именно форматирование, не могу, т.к. писалось оно еще с 2008-года, т.к. тогда нужна была поддержка не только std строк, но и MFC/ATL — строк и т.д. Код там не для показа, честно. Я по мере сил выкидываю от туда всё, что появляется в новых стандартах С++, изолирую законченные части и выкладываю в опен сорс, например:
slimcpplib — вот
sutfcpplib — и вот

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

Где угодно пишем:
archive_t arch = pack(custom_class{}, as<archive_t>);

ADL ищет pack сначала в текущем пространстве имен, потом с пространстве имен custom_class, а если не найдет то в пространстве имен archive_t — моем стандартном. Эти три пространсва имен могут быть разными. Отсюда и гибкость.
Отредактировано 27.10.2022 12:40 Videoman . Предыдущая версия . Еще …
Отредактировано 27.10.2022 12:38 Videoman . Предыдущая версия .
Re[13]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 13:29
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Ну чо вы такой душный, а?!


А я вообще старый и мерзкий старикашка.

V>В двух словах:


V>
V>archive_t arch = pack(custom_class{}, as<archive_t>);
V>


Как это перенести на вот такой способ записи:
int a = ...;
std::time_t b = ...;
print(std::cout, "Value {} at {}", a, b);

если я хочу time_t форматировать не так, как это делает некий задействованный мной print (пусть он будет стандартным)?

Здесь же есть только форматная строка (которая парсится не мной) и ссылки на значения, типы которых могут вообще не иметь отношения к моей библиотеке. Что-то мне не видно где здесь вообще форматеры для print-а указать можно.
Re[14]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 14:22
Оценка: 8 (1)
Здравствуйте, so5team, Вы писали:

S>Как это перенести на вот такой способ записи:

S>
S>int a = ...;
S>std::time_t b = ...;
S>print(std::cout, "Value {} at {}", a, b);
S>

S>если я хочу time_t форматировать не так, как это делает некий задействованный мной print (пусть он будет стандартным)?

S>Здесь же есть только форматная строка (которая парсится не мной) и ссылки на значения, типы которых могут вообще не иметь отношения к моей библиотеке. Что-то мне не видно где здесь вообще форматеры для print-а указать можно.


Могу рассуждать только гипотетически. Допустим мы можем добавить более низкоуровневую функцию print_as:

namespace my_namespace
{
struct my_trait{};
}

namespace std
{
std::print_as(std::cout, std::format_as<my_namespace::my_trait>(), "Value {} at {}", a, b);
}
Где-то в потрохах мы делегируем std::format_as<my_trait>() до каждого форматера, который является функцией и получает его в качестве аргумента. Тогда если отключены стандартные форматеры, ADL будет искать во всех остальных местах, в том числе в my_namespace. А std::print для удобства пусть вызывает что-то типа: std::print_as(std::cout, std::format_as<std::format_traits>, "Value {} at {}", a, b);
Re[10]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 14:32
Оценка:
Здравствуйте, so5team, Вы писали:

S>Мы все еще говорим про fmtlib и std::format?


S>Можно хотя бы одну цитату из моих слов, из которой бы следовало, что я защищаю std::format (или какое-либо из технических решений, примененных в std::format)?


Выглядит что защищаете, потому-что не хотите признать что некоторые вещи нельзя сделать, а некоторые непонятно как делать в теперь уже отлитой в камне парадигме. И
  вот чувак из Microsoft
url=https://www.youtube.com/watch?v=zssTF1uhxtM&amp;list=LL&amp;index=6&amp;ab_channel=CppCon]Why does std::format do that? — Charlie Barto — CppCon 2021[/url]

прямо докладывает, что особо сильно не думали, увидели хорошую, я не спорю, библиотеку, она им понравилась и они её прямо почти один в один перенесли в стандарт. Предложение было от Microsoft и они же её первыми заимплементили для VC.
Re[15]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 15:45
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Где-то в потрохах мы делегируем std::format_as<my_trait>() до каждого форматера, который является функцией и получает его в качестве аргумента. Тогда если отключены стандартные форматеры, ADL будет искать во всех остальных местах, в том числе в my_namespace. А std::print для удобства пусть вызывает что-то типа: std::print_as(std::cout, std::format_as<std::format_traits>, "Value {} at {}", a, b);


И что мешает внести такое предложение? Хотя бы вот сюда: https://stdcpp.ru/proposals/
Если людей это заинтересует, то они доведут это до формального предложения комитету.

Меня в таком подходе, например, сильно смущает такой момент: допустим, мы линкуем два объектника. В одном используются вызовы std::print (которые трансформируются в std::print_as(..., std::format_as<std::format_traits>, ...)), а в другом вызовы std::print_as(.., std::format_as<my_traits>, ...). Поскольку std::print_as шаблонная, то компилятор сгенерирует и std::print_as для std::format_traits, и для my_traits. Т.е. мы получим две копии практически одного и того же кода. На ровном месте, по сути.
Re[11]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 15:50
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Мы все еще говорим про fmtlib и std::format?


S>>Можно хотя бы одну цитату из моих слов, из которой бы следовало, что я защищаю std::format (или какое-либо из технических решений, примененных в std::format)?


V>Выглядит что защищаете


Т.е. цитат нет, но выглядит?

V>потому-что не хотите признать что некоторые вещи нельзя сделать, а некоторые непонятно как делать в теперь уже отлитой в камне парадигме.


Откуда следует что я что-то признаю или не признаю? С цитатами, плз.

V>прямо докладывает, что особо сильно не думали, увидели хорошую, я не спорю, библиотеку, она им понравилась и они её прямо почти один в один перенесли в стандарт. Предложение было от Microsoft и они же её первыми заимплементили для VC.


Т.е. роль Microsoft-а лишь в том, что они взяли на себя труд провести fmtlib через процедуру стандартизации. Но про это обязательно нужно высказаться вот в таком духе:

> Ребята из Microsoft запилили библиотеку для своих личных нужд и пропихнули её в стандарт, т.к. могли.


Добавить сюда еще и:

> А я говорил, что люди принимающие стандарты, по ходу вообще не думают, последнее время.


и чуть ли не картина маслом вырисовывается.
Re[16]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 27.10.22 16:44
Оценка:
Здравствуйте, so5team, Вы писали:

S>И что мешает внести такое предложение? Хотя бы вот сюда: https://stdcpp.ru/proposals/

S>Если людей это заинтересует, то они доведут это до формального предложения комитету.

Теоретически ничего не мешает. Практически, я только неделю как погрузился в std::format и пока изучаю исходники и занят своей работой. Что бы оформить предложение, нужно время. Время появиться, обязательно сделаю, тем более если вы считаете, что это имеет смысл и что это вообще кто-то читает.

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

S>Меня в таком подходе, например, сильно смущает такой момент: допустим, мы линкуем два объектника. В одном используются вызовы std::print (которые трансформируются в std::print_as(..., std::format_as<std::format_traits>, ...)), а в другом вызовы std::print_as(.., std::format_as<my_traits>, ...). Поскольку std::print_as шаблонная, то компилятор сгенерирует и std::print_as для std::format_traits, и для my_traits. Т.е. мы получим две копии практически одного и того же кода. На ровном месте, по сути.


Ну это стандартные издержки шаблонов, можно с легкостью нагенерировать кучу машинного кода. Зато быстро ! Нужно будет подумать как это оптимизировать. Например, поскольку аргумент формальный, его можно передавать как параметр шаблона и только в конце инстанцировать при передаче в сам formatter. Такое компилятору будет проще оптимизировать?
Re[17]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 27.10.22 17:01
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Подход с функциями хорош еще и тем, что работают стандартные преобразования.


Я вот хоть убей не вижу где же здесь функции.
Re: Cоздание базового шаблона минуя специализацию
От: Sm0ke Россия ksi
Дата: 27.10.22 20:30
Оценка:
Здравствуйте, Videoman, Вы писали:

Заведите второй параметр шаблона с именем traits_t. Заведите разные траитсы. И при специализации для int указывай нужный траит.

template <typename type_t, template <typename> typename traits_t>
struct object {
    using t_traits = traits_t<type_t>;
    void print() { t_traits::print(this); }
};

template <typename type_t>
struct trait1 {
    static void print(type_t * p) { std::cout << "trait 1"; }
};

template <typename type_t>
struct trait2 {
    static void print(type_t * p) { std::cout << "trait 2"; }
};

int main() {
    object<int, trait1> obj;
    obj.print();
    return 0;
}
Re[2]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 06:51
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Заведите второй параметр шаблона с именем traits_t.


Боюсь, это не тот случай, когда это осуществимо. Стартовое сообщение хоть и сформулировано для общего случая, но по дальнейшему обсуждению ясно, что речь идет об std::formatter, primary template которого объявлен в стандартной библиотеке.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[18]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 07:13
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Я вот хоть убей не вижу где же здесь функции.


Так о том и речь, что их нет, а вместо функций — шаблон класса std::formatter. А если бы std::format для кастомизации использовал бы не шаблон класса со специализациями, а неквалифицированный вызов функции с каким-то предопределенным именем, это могло бы дать ряд преимуществ:


Если что-то и упустил, по-моему, этого списка уже достаточно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[19]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 07:15
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, so5team, Вы писали:


S>>Я вот хоть убей не вижу где же здесь функции.


R>Так о том и речь, что их нет, а вместо функций — шаблон класса std::formatter. А если бы std::format для кастомизации использовал бы не шаблон класса со специализациями, а неквалифицированный вызов функции с каким-то предопределенным именем, это могло бы дать ряд преимуществ:


Вынужден повторить то, что уже писал: очень не хватает примеров. У вас одна картина в голове, у Videoman вторая, у меня третья. Все это можно было бы привести к общему знаменателю несколькими примерами, но вот примеров-то никто и не приводит
Re[20]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 07:29
Оценка:
Здравствуйте, so5team, Вы писали:

S>Вынужден повторить то, что уже писал: очень не хватает примеров. У вас одна картина в голове, у Videoman вторая, у меня третья. Все это можно было бы привести к общему знаменателю несколькими примерами, но вот примеров-то никто и не приводит


Ну, я изложил свой взгляд на предмет и надеялся, что словесного описания будет достаточно. На примеры сейчас времени не хватает, сорри.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[19]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 07:41
Оценка:
Здравствуйте, rg45, Вы писали:

R>Как частный случай — форматтер, определенный для базового класса автоматом будет работать и для всех производных, для которых не предоставлена своя собственая версия форматтера;


Это вроде как и сейчас в fmtlib возможно (полагаю, что и в std::format). Пример прямо из документации к fmtlib:
#include <type_traits>
#include <fmt/format.h>

struct A {
  virtual ~A() {}
  virtual std::string name() const { return "A"; }
};

struct B : A {
  virtual std::string name() const { return "B"; }
};

template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
    fmt::formatter<std::string> {
  template <typename FormatCtx>
  auto format(const A& a, FormatCtx& ctx) const {
    return fmt::formatter<std::string>::format(a.name(), ctx);
  }
};

int main() {
  B b;
  A& a = b;
  fmt::print("{}", a); // prints "B"
}


https://godbolt.org/z/1G1bhhfso
Re[21]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 07:43
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну, я изложил свой взгляд на предмет и надеялся, что словесного описания будет достаточно.


Так дьявол-то в деталях. А без деталей получается как в анекдоте "мыши, станьте ежиками!"

R>На примеры сейчас времени не хватает, сорри.


Вряд ли в этом вопросе кто-то куда-то торопится.
Re[20]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 08:19
Оценка: +1
Здравствуйте, so5team, Вы писали:

R>>Как частный случай — форматтер, определенный для базового класса автоматом будет работать и для всех производных, для которых не предоставлена своя собственая версия форматтера;


S>Это вроде как и сейчас в fmtlib возможно (полагаю, что и в std::format). Пример прямо из документации к fmtlib:


Это понятно, что выкрутиться можно. Но если в будущем появляется еще и тип C, также производный от A, и вы попытаетесь написать для него форматтер, похожий на тот, который уже написали для A, вы тут же почувствуете неудобство — этого нельзя будет сделать, не модифицировав специализацию форматтера для A, потому что обе специализации окажутся равноправны и создадут коллизию. Этого не произошло бы с функциями.

P.S. И виртуальность вы напрасно сюда внесли, имхо. Я изначально вел речь про обычное невиртуальное использование форматирования и виртуальность здесь вносит только лишний повод для побочных дискуссий.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 8:22 rg45 . Предыдущая версия .
Re[22]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 08:38
Оценка:
Здравствуйте, so5team, Вы писали:

R>>Ну, я изложил свой взгляд на предмет и надеялся, что словесного описания будет достаточно.

S>Так дьявол-то в деталях. А без деталей получается как в анекдоте "мыши, станьте ежиками!"
R>>На примеры сейчас времени не хватает, сорри.
S>Вряд ли в этом вопросе кто-то куда-то торопится.

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

Примеры постараюсь предоставить, но чуть попозже.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[21]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 08:46
Оценка:
Здравствуйте, rg45, Вы писали:

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


Это вообще зыбкая почва для споров. Если мы оперируем традиционным полиморфизмом, то надобность в отдельном форматтере для C вызывает вопросы. Если мы используем наследование не для полиморфизма, а для переиспользования функционала из базового класса в производных, то вопросы вызывает форматтер для A, который может форматировать и экземпляры классов-наследников.

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

Не пытаясь защищать fmtlib и std::format (мне процедура написания собственных специализаций formatter-а кажется неудобной) все же есть ощущение, что в процессе эволюции fmtlib и прохождение через комитет наверняка высказывались самые разные идеи, а на практике выжила имеющаяся комбинация возможностей + сложность реализации + накладные расходы.

R>P.S. И виртуальность вы напрасно сюда внесли, имхо.


Пример из документации к fmtlib, я его просто скопипастил. Все претензии к Зверовичу.
Re[22]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 08:56
Оценка:
Здравствуйте, so5team, Вы писали:

S>Это вообще зыбкая почва для споров. Если мы оперируем традиционным полиморфизмом, то надобность в отдельном форматтере для C вызывает вопросы.


Да, только этих вопросов даже не возникло бы в случае использования функций. Может, для начала стоит ответить на вопрос, почему вместо фунций необходимо использовать шаблон класса? Имхо, прикладная библиотека не должна диктовать разработчику вопросы дизайна. Тем более в области "зыбких почв"
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 9:08 rg45 . Предыдущая версия . Еще …
Отредактировано 28.10.2022 9:02 rg45 . Предыдущая версия .
Re[23]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 09:13
Оценка:
Здравствуйте, rg45, Вы писали:

S>>Это вообще зыбкая почва для споров. Если мы оперируем традиционным полиморфизмом, то надобность в отдельном форматтере для C вызывает вопросы.


R>Да только этих вопросов даже не возникло бы в случае использования функций.


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

А вот некий гипотетический дизайн на функциях он же идеален, в нем нет просчетов. Как можно обосрать то, чего нет?

R>Может, для начала стоит ответить на вопрос, почему вместо фунций необходимо использовать шаблон класса?


Были бы функции, можно было бы ответить. Но их нет.

У fmt::formatter, например, может быть состояние. Т.е. в процессе работы fmt::formatter::parse можно что-то сохранить/вычислить, что затем будет использовано в fmt::formatter::format. Причем наличие/отсутствие состояния совершенно прозрачно для самого fmt::format/print.

Тогда как функции stateless, и если из parse нужно что-то передать в format, то здесь уже нужно озадачиваться тем, чтобы функции были парными. Условно:
void parse(parsing_ctx &);
void format(const T & v, formatting_ctx &);

some_state parse(parsing_ctx &);
void format(const some_state & state, const T & v, formatting_ctx &);


И для меня не очевидно, что свободные функции в массовом применении в таком случае будут проще в использовании, чем специализации fmt::formatter.

R>Имхо, прикладная библиотека не должна диктовать разработчику вопросы дизайна.


Простите, но это уже демагогия. Любой существующий инструмент будет накладывать на пользователя какие-то ограничения. И с этим придется считать, возможно, и на уровне дизайна.
Re[24]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 09:57
Оценка:
Здравствуйте, so5team, Вы писали:

S>Простите мне мой французский, но тут явно неравные условия. Есть конкретная fmtlib, которую можно пощупать и, поскольку она есть и опыт ее использования накоплен, то можно запросто обосрать либо ее всю, либо отдельные ее части.

S>А вот некий гипотетический дизайн на функциях он же идеален, в нем нет просчетов. Как можно обосрать то, чего нет?

То есть, если я вижу какие-то конкретные минусы какой-то конкретной библиотеки, я не имею права об этом рассуждать до тех пор, пока не предоставлю рабочую альтернативу? Не жестковата ли постановка вопроса?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[24]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 10:11
Оценка:
Здравствуйте, so5team, Вы писали:

S>Тогда как функции stateless, и если из parse нужно что-то передать в format, то здесь уже нужно озадачиваться тем, чтобы функции были парными. Условно:

S>И для меня не очевидно, что свободные функции в массовом применении в таком случае будут проще в использовании, чем специализации fmt::formatter.

А для меня это полностью очевидно. В случае с функциями, для стандартной библиотеки вообще не составит труда поддержать передачу состояния, причем сделать это опционально — поддержать stateless/statеfull тем же самым путем, как осуществить пооддержку свободных функций, так и функций-членов. Да, это дополнительные телодвижения, но они делаются один раз внутри стандартной библиотеки. С точки зрения пользователя все получается удобно и комфортно. Да нужно будет обеспечивать консистентность пользовательских parse и формат, но для этого совсем не обязательно делать из всегда парными. И это гораздо удобнее, чем карячить специализации классов с конфликтами, разрывами неймспейсов и мозгов.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 10:15 rg45 . Предыдущая версия .
Re[19]: Cоздание базового шаблона минуя специализацию
От: Went  
Дата: 28.10.22 10:34
Оценка: :))
Здравствуйте, rg45, Вы писали:
R>Так о том и речь, что их нет, а вместо функций — шаблон класса std::formatter. А если бы std::format для кастомизации использовал бы не шаблон класса со специализациями, а неквалифицированный вызов функции с каким-то предопределенным именем, это могло бы дать ряд преимуществ...
Наверное, я ошибаюсь, но все-таки спрошу. А нет ли опасности, что перегрузки этой функции — форматтера, объявленные после определения функции std::format, окажутся вне области её видимости и не будут вызываться? Или есть способ как-то просто обойти эту проблему?
Re[20]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 10:44
Оценка: 4 (1)
Здравствуйте, Went, Вы писали:

W>Наверное, я ошибаюсь, но все-таки спрошу. А нет ли опасности, что перегрузки этой функции — форматтера, объявленные после определения функции std::format, окажутся вне области её видимости и не будут вызываться? Или есть способ как-то просто обойти эту проблему?


Как я догадываюсь, твой вопрос уходит корнями в ту проблему, которую ты описывал здесь: http://rsdn.org/forum/cpp/7906979.1
Автор: Went
Дата: 16.12.20
?

Только в том случае у тебя возникли проблемы со встроенными типами. С типами, определенными пользователем, таких проблем не будет, ADL исправно будет работать. Равно как и для типов, в которых пользовательские типы фигурируют в качестве шаблонных параметров (std::vector<MyClass> и т.п.). Ну если только не косячить с неймспейсами, разумеется.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 10:45 rg45 . Предыдущая версия .
Re[20]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 28.10.22 11:14
Оценка: +1
Здравствуйте, Went, Вы писали:

W>Наверное, я ошибаюсь, но все-таки спрошу. А нет ли опасности, что перегрузки этой функции — форматтера, объявленные после определения функции std::format, окажутся вне области её видимости и не будут вызываться? Или есть способ как-то просто обойти эту проблему?


Сейчас также все не сахар. Форматер в виде специализации класса не вызывается и всё, пока ты правильно не подберёшь синтаксис перегрузки. С шаблонными классами это бывает не очень просто. Вот пример у меня в коде:
namespace htlib::v2
{
template<typename type_t>
struct is_point {

    static std::false_type test(...);
    template<htlib::v2::uint_t dimensions, typename other_t>
    static std::true_type test(const htlib::v2::pointxd_t<dimensions, other_t>&);

    static constexpr bool value = decltype(test(std::declval<type_t>()))::value;
};
template<typename type_t>
inline constexpr bool is_point_v = is_point<type_t>::value;

} // namespace htlib::v2

template<typename char_t, typename point_t>
class std::formatter<point_t, char_t, std::enable_if_t<htlib::v2::is_point_v<point_t>>>
{
//...
}
Вот такие мета-функции приходится писать что бы написать форматер для всех наследников базового шаблонного класса.

Хотя в случае с функциями была простая перегрузка, типа:
template<htlib::v2::uint_t dimensions, typename other_t>
void formatter(const htlib::v2::pointxd_t<dimensions, other_t>&);
Re[25]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 11:35
Оценка:
Здравствуйте, rg45, Вы писали:

R>То есть, если я вижу какие-то конкретные минусы какой-то конкретной библиотеки, я не имею права об этом рассуждать до тех пор, пока не предоставлю рабочую альтернативу? Не жестковата ли постановка вопроса?


Только такая постановка вопроса позволяет вести конструктивный разговор, сорри.

Просто сказать, что дизайн со специализацией formatter -- говно, а те, кто его стандартизировал, сделали это неподумавши, -- это лишь выпустить пар в свисток. Ибо нет никакого способа исправить ситуацию.

То, что можно сделать лучше, чем в fmt/std::format, с точки зрения теории, не подлежит сомнению. Но интересна не теория, а практика: как это будет выглядеть, сколько будет стоить.
Re[25]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 11:38
Оценка:
Здравствуйте, rg45, Вы писали:

R>С точки зрения пользователя все получается удобно и комфортно.


Раз.

R>Да нужно будет обеспечивать консистентность пользовательских parse и формат,


Два.

Только мне кажется, что "два" противоречит "раз"?

R>но для этого совсем не обязательно делать из всегда парными.


А какой в этом смысл?

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


Разрый неймспейса -- да, серьезное неудобство. Регулярно на это чертыхаюсь.

Примеры конфликтов можно?
Re[26]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 28.10.22 12:02
Оценка:
Здравствуйте, so5team, Вы писали:

S>Только мне кажется, что "два" противоречит "раз"?


Зачем нужно две функции, в том числе в текущей реализации fmt, понятно. Функция форматирования работает на этапе компиляции, т.е. она constexpr и выдает ошибки там же.
На самом деде не проблема сlелать static_assert с выдачей ошибки в случае отсутствия одной из функций. Увидели тип, какой-то функции нет — тут же static_assert.

S>Разрый неймспейса -- да, серьезное неудобство. Регулярно на это чертыхаюсь.


Разрыв неймспейса, это как раз специализация форматтер-а в пространстве имен std. Со свободными функциями это не нужно. Описываете функцию в своём же пространстве имен, ADL её найдет сам.
Re[27]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 12:15
Оценка:
Здравствуйте, Videoman, Вы писали:

S>>Только мне кажется, что "два" противоречит "раз"?


V>Зачем нужно две функции, в том числе в текущей реализации fmt, понятно. Функция форматирования работает на этапе компиляции, т.е. она constexpr и выдает ошибки там же.

V>На самом деде не проблема сlелать static_assert с выдачей ошибки в случае отсутствия одной из функций. Увидели тип, какой-то функции нет — тут же static_assert.

Э... А зачем вы это все написали? Это типа доказательство удобства иметь две свободные функции, которые пользователю нужно держать согласованными?
Re[26]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 12:23
Оценка: +1
Здравствуйте, so5team, Вы писали:

R>>С точки зрения пользователя все получается удобно и комфортно.


S>Раз.


R>>Да нужно будет обеспечивать консистентность пользовательских parse и формат,


S>Два.


S>Только мне кажется, что "два" противоречит "раз"?


А по-моему, совсем нет, если требуется состояние, то его нужно вернуть из parse, и получить в format параметром, если не нужно, значит ничего этого не нужно. Что может быть проще и естественнее?

S>Разрый неймспейса -- да, серьезное неудобство. Регулярно на это чертыхаюсь.


Так о чем и речь. Одно это перевешивает все остальное.

S>Примеры конфликтов можно?


Об одном случае я уже упоминал выше
Автор: rg45
Дата: 28.10.22
, вот эти две специализации одновременно могут подходить для одних и тех же типов:

template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> {};

template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<С, T>::value, char>> {};


И важно то, что проблема не сводится к одному лишь наследованию и не решается отказом от него. Я уверен многим из присутствующих приходилось разруливать самые разнообразные SFINAE-коллизии в виду того, что для одного типа подходят более одной специализации. Для функций этот вопрос во многих случаях реашется проще, благодаря механизму выбора кандидата, обеспечивающего лучшее соответствие типов формальных и фактических параметров. А при использовании SFINAE при специализации классов, фильтры нужно "полировать" при расширении, чтоб исключить накладки. Да я уверен, что вы и сами знаете об этой проблеме.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[27]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 12:34
Оценка:
Здравствуйте, rg45, Вы писали:

S>>Только мне кажется, что "два" противоречит "раз"?


R>А по-моему, совсем нет, если требуется состояние, то его нужно вернуть из parse, и получить в format параметром, если не нужно, значит ничего этого не нужно. Что может быть проще и естественнее?


Как раз подход с классом, у которого есть методы. Хотим, делаем состояние. Хотим не делаем. Хотим, делаем в зависимости от defines и фазы Луны. При этом прототипы самих методов не меняются.

При этом, в моей практике, при специализации fmt::formatter можно наследоваться от уже существующего fmt::formatter и переопределять только один из методов.

А в случае функций нужно писать две функции. И при этом их еще и модифицировать, если вдруг выяснилось, что состояние (не) нужно.

S>>Разрый неймспейса -- да, серьезное неудобство. Регулярно на это чертыхаюсь.


R>Так о чем и речь. Одно это перевешивает все остальное.


Э... Ну, ОК.

S>>Примеры конфликтов можно?


R>Об одном случае я уже упоминал выше
Автор: rg45
Дата: 28.10.22
, вот эти две специализации одновременно могут подходить для одних и тех же типов:


Т.е. пример а) искусственный и b) спорный.

Videoman привел свой пример из той же оперы. Но есть у меня ощущение, что там что-то человек сам перемудрил. Впрочем, могу ошибаться, чтобы убедиться в этом нужно глубоко в его код погрузиться.
Re[28]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 12:36
Оценка:
Здравствуйте, so5team, Вы писали:

S>А в случае функций нужно писать две функции.


Совсем не обязательно. Об этом я уже писал.

S>Т.е. пример а) искусственный и b) спорный.


Т.е. то, что написано после примера, вы проигнорировали. Ну ОК.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[29]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 12:39
Оценка:
Здравствуйте, rg45, Вы писали:

R>Т.е. то, что написано после примера, вы проигнорировали. Ну ОК.


Простите, общие слова не интересны. Конкретные примеры -- да, абстрактные рассуждения о том, как у кого-то где-то что-то -- нет.
Re[30]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 12:43
Оценка:
Здравствуйте, so5team, Вы писали:

S>Простите, общие слова не интересны. Конкретные примеры -- да, абстрактные рассуждения о том, как у кого-то где-то что-то -- нет.


А чего вы решаете за всех что интересно, что нет? Это ж форум, а не личная переписка.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 12:43 rg45 . Предыдущая версия .
Re[31]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 28.10.22 12:56
Оценка:
Здравствуйте, rg45, Вы писали:

S>>Простите, общие слова не интересны. Конкретные примеры -- да, абстрактные рассуждения о том, как у кого-то где-то что-то -- нет.


R>А чего вы решаете за всех что интересно, что нет?


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

Вроде бы по другому и не бывает?

R>Это ж форум, а не личная переписка.


Именно. Я же не затыкаю вам рот, не указываю что говорить, а что нет.
То, что написано вами здесь и остается и если кто-то заинтересуется тем, что вы высказали, то этот кто-то сможет продолжить разговор с заинтересовавшего его (её) места.
Re[21]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 28.10.22 14:26
Оценка: +1
Здравствуйте, Videoman, Вы писали:

V>Хотя в случае с функциями была простая перегрузка, типа:
V>template<htlib::v2::uint_t dimensions, typename other_t>
V>void formatter(const htlib::v2::pointxd_t<dimensions, other_t>&);
V>


+1

Или вот еще интересный сценарий: подход с функциями позволяет, например, легко определить один общий форматтер (или фиксированный набор форматтеров) для всех пользовательских типов, объединенных общим пространством имен. Это может быть как простая шаблонная функция, так и просто using declaration, вносящее имя функции, определенной в каком-то другом пространстве имен. (И разумеется, никто при этом не запрещает использовать в этом же пространсве имен и другие, более специальные форматтеры наряду с общим, если нужно). В подходе со специализациями шаблона класса придется написать тонны кода и постоянно расширять при добавлении новых типов.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.10.2022 14:32 rg45 . Предыдущая версия . Еще …
Отредактировано 28.10.2022 14:31 rg45 . Предыдущая версия .
Отредактировано 28.10.2022 14:30 rg45 . Предыдущая версия .
Отредактировано 28.10.2022 14:29 rg45 . Предыдущая версия .
Re[22]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 28.10.22 18:01
Оценка: +2
Здравствуйте, rg45, Вы писали:

R>Или вот еще интересный сценарий: подход с функциями позволяет, например, легко определить один общий форматтер (или фиксированный набор форматтеров) для всех пользовательских типов, объединенных общим пространством имен. Это может быть как простая шаблонная функция, так и просто using declaration, вносящее имя функции, определенной в каком-то другом пространстве имен. (И разумеется, никто при этом не запрещает использовать в этом же пространсве имен и другие, более специальные форматтеры наряду с общим, если нужно). В подходе со специализациями шаблона класса придется написать тонны кода и постоянно расширять при добавлении новых типов.


Да, все богатые возможности перегрузки функций идут лесом. Сейчас, переписывая форматеры своих типов я постоянно налетаю на грабли ограничении специализации класса.
Re[19]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 29.10.22 06:43
Оценка: 20 (3)
Здравствуйте, rg45, Вы писали:

R>Так о том и речь, что их нет, а вместо функций — шаблон класса std::formatter. А если бы std::format для кастомизации использовал бы не шаблон класса со специализациями, а неквалифицированный вызов функции с каким-то предопределенным именем, это могло бы дать ряд преимуществ:


Могу ошибаться, но может быть есть способ объединить лучшее из двух миров?

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

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

Небольшой набросок слепленный на коленке для того, чтобы продемонстрировать идею:
  откровенный колхозинг
#include <algorithm>
#include <iostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <sstream>

namespace fmtlib
{

template<typename T, typename CharT = char>
struct formatter;

template<typename CharT, typename T>
[[nodiscard]]
formatter<T, CharT>
make_formatter(const T &);

template<typename CharT, typename T>
CharT *
format(CharT * dest, T && value)
    {
        return make_formatter<CharT>(value).format(dest, value);
    }

template<>
struct formatter<std::string_view, char>
    {
        char *
        format(char * dest, const std::string_view & v)
            {
                *(dest++) = '"';
                std::copy(v.begin(), v.end(), dest);
                dest += v.size();
                *(dest++) = '"';
                return dest;
            }
    };

template<>
struct formatter<std::string, char>
    :    public formatter<std::string_view, char>
    {
        char *
        format(char * dest, const std::string & v)
            {
                return formatter<std::string_view, char>::format(
                        dest, std::string_view{v});
            }
    };

template<>
struct formatter<char, char>
    {
        char *
        format(char * dest, const char v)
            {
                *(dest++) = v;
                return dest;
            }
    };

template<typename T>
struct formatter<T, std::enable_if_t<std::is_arithmetic_v<T>, char>>
    {
        char *
        format(char * dest, const T & v)
            {
                std::ostringstream ss;
                ss << v;
                const auto sv = ss.str();
                std::copy(sv.begin(), sv.end(), dest);
                dest += sv.size();
                return dest;
            }
    };

template<typename CharT, typename T>
[[nodiscard]]
formatter<T, CharT>
make_formatter(const T &)
    {
        return {};
    }

} /* namespace fmtlib */

namespace one
{

template<typename T>
struct point
    {
        T x;
        T y;
    };

} /* namespace one */

template<typename T>
struct fmtlib::formatter<one::point<T>, char>
    {
        char *
        format(char * dest, const one::point<T> & v)
        {
            dest = fmtlib::format(dest, '{');
            dest = fmtlib::format(dest, v.x);
            dest = fmtlib::format(dest, ',');
            dest = fmtlib::format(dest, v.y);
            dest = fmtlib::format(dest, '}');
            return dest;
        }
    };

namespace two
{

template<typename T, typename Tag>
struct value_holder
    {
        T v;
    };

template<typename T, typename Tag, typename CharT>
struct value_holder_formatter
    {
        CharT *
        format(CharT * dest, const value_holder<T, Tag> & v)
        {
            return fmtlib::formatter<T, CharT>{}.format(dest, v.v);
        }
    };

template<typename CharT, typename T, typename Tag>
value_holder_formatter<T, Tag, CharT>
make_formatter(const value_holder<T, Tag> &)
    {
        return {};
    }

} /* namespace two */

int
main()
    {
        using namespace std::string_literals;
        using namespace std::string_view_literals;

        char buf[1024];

        struct tag_1 {};
        struct tag_2 {};

        char * p = fmtlib::format(buf, 42);
        p = fmtlib::format(p, ' ');
        p = fmtlib::format(p, "is the answer"s);
        p = fmtlib::format(p, ';' );
        p = fmtlib::format(p, one::point<int>{3,4});
        p = fmtlib::format(p, '-' );
        p = fmtlib::format(p, one::point<float>{0.1,-1.2});
        p = fmtlib::format(p, ';' );
        p = fmtlib::format(p, two::value_holder<short, tag_1>{333});
        p = fmtlib::format(p, '-' );
        p = fmtlib::format(p, two::value_holder<unsigned long long, tag_2>{555});
        *p = 0;

        std::cout << "Result: " << buf << std::endl;
    }

Проверялось на g++-11 в режиме C++17


Не знаю насколько реально все это впилить в fmtlib, т.к. там тип formatter нужен для вложенного типа basic_format_context::formatter_type. Но если реально, то можно расширить функциональность fmtlib сохранив при этом совместимость с уже имеющимся кодом, в котором делаются специализации fmt::formatter. И, соответственно, подобный же трюк можно будет проделать и с std::format.
Re[20]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 29.10.22 10:03
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Могу ошибаться, но может быть есть способ объединить лучшее из двух миров?


S>Идея в том, что сейчас fmtlib просто инстанциирует тип formatter там, где ей это нужно. Наверное, можно создавать formatter не напрямую, а вызвав функцию-фабрику, которая уже и возвратит экземпляр formatter.


S>Функция-фабрика будет свободной функцией, которая может быть определена и в пространстве имен пользователя.


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


В конце концов, с таким подходом пользователь может самостоятельно осуществить переход от форматтеров-классов к форматтерам-функциям, если очень хочется. А может использовать какие-то более сложные гибридные схемы. Вместо одной точки кастомизации теперь их целых три: специализация библиотечного шаблона класса; фабрики форматтеров; пользовательский форматтер. Пользовательский форматтер вообще можно навернуть с какой угодно сложностью и обеспечить еще 100500 точек кастомизации, специфичных для прикладной области. В целом гибкость и юзабельность существенно лучше, чем в текущем подходе со специализациями.

--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 29.10.2022 11:05 rg45 . Предыдущая версия . Еще …
Отредактировано 29.10.2022 11:03 rg45 . Предыдущая версия .
Отредактировано 29.10.2022 10:58 rg45 . Предыдущая версия .
Отредактировано 29.10.2022 10:57 rg45 . Предыдущая версия .
Отредактировано 29.10.2022 10:56 rg45 . Предыдущая версия .
Отредактировано 29.10.2022 10:31 rg45 . Предыдущая версия .
Отредактировано 29.10.2022 10:04 rg45 . Предыдущая версия .
Re[20]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 29.10.22 18:35
Оценка:
Здравствуйте, so5team, Вы писали:

S>Идея в том, что сейчас fmtlib просто инстанциирует тип formatter там, где ей это нужно. Наверное, можно создавать formatter не напрямую, а вызвав функцию-фабрику, которая уже и возвратит экземпляр formatter.


S>Функция-фабрика будет свободной функцией, которая может быть определена и в пространстве имен пользователя.


S>
S>template<typename T, typename CharT = char>
S>struct formatter;
S>


Можно сделать еще один шажок навстречу пользователю и для основного шаблона formatter предоставить определение, которое будет делегировать вызов (через ADL) пользовательстким функциям parse и format. Тогда у пользователя будет три возможности: специализировать библиотечный formatter, определить собственный форматтер с фабрикой, либо просто определить функции parse и format в том же пространстве имен, что и пользовательский тип (типы). Это для тех типов, которые не требуют передачи состояния между parse и format. Что-то мне подсказывает, что таких типов будет большинство.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 29.10.2022 18:48 rg45 . Предыдущая версия .
Re[20]: Cоздание базового шаблона минуя специализацию
От: _NN_ www.nemerleweb.com
Дата: 29.10.22 21:44
Оценка: 1 (1) +1
Здравствуйте, so5team, Вы писали:

Может в https://stdcpp.ru стоит написать ?
А там гляди и лет через 10 в стандарт придёт.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[21]: Cоздание базового шаблона минуя специализацию
От: so5team https://stiffstream.com
Дата: 30.10.22 06:00
Оценка: +1
Здравствуйте, _NN_, Вы писали:

_NN>Может в https://stdcpp.ru стоит написать ?

_NN>А там гляди и лет через 10 в стандарт придёт.

ИМХО, в данном случае логичнее было бы идти через модификацию fmtlib. Если такое изменение в принципе возможно, то внедрить его будет гораздо быстрее, затем можно будет собрать актуальный фидбэк от пользователей, и уже на основании фидбэка обновлять стандарт. Тем более, что скоуп C++23 уже вроде как зафиксировали, новое туда добавлять не будут. А до C++26 можно будет потренироваться на кошках.

Только вот в ближайшее пару месяцев у меня лично возможности заняться pull-request-ом для fmtlib не будет
Re[3]: Cоздание базового шаблона минуя специализацию
От: PM  
Дата: 30.10.22 08:27
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Наверное вопрос в этом, как выкрутится теперь. Ситуация простая. У std::formatter есть специализация для вывода std::chrono::duration<>. У меня в библиотеках везде используется своя специализация времени:
V>using reftime_t = std::chrono::duration<int64, std:ratio<1, 10000000>>;
V>
Стандартные возможности форматирования огромны, но мне хочется их расширить для своего типа времени и переиспользовать. Таким образом объявляя
V>template <>
V>struct std::formatter<reftime_t> {
V>    // ...
V>};
V>
я автоматически лишаюсь возможности вызвать стандартную реализацию и вынужден повторять всю логику заново. Это не дело, я считаю


Смотрю код новой библиотеки логирования xtr. В ней чтобы форматировать `std::timespec` по-своему, автор сделал свой тип простым наследованием от стандартной структуры:
namespace xtr
{
    struct timespec : std::timespec
    {
        timespec() = default;

        timespec(std::timespec ts) : std::timespec(ts)
        {
        }
    };
}

template<>
struct fmt::formatter<xtr::timespec>
{
    template<typename ParseContext>
    constexpr auto parse(ParseContext& ctx)
    {
        return ctx.begin();
    }

    template<typename FormatContext>
    auto format(const xtr::timespec ts, FormatContext& ctx)
    {
        std::tm temp;
        return fmt::format_to(
            ctx.out(),
            "{:%Y-%m-%d %T}.{:06}",
            *::gmtime_r(&ts.tv_sec, &temp),
            ts.tv_nsec / 1000);
    }
};

https://github.com/choll/xtr/blob/master/single_include/xtr/logger.hpp#L70L105
Re[21]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 30.10.22 11:40
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>В конце концов, с таким подходом пользователь может самостоятельно осуществить переход от форматтеров-классов к форматтерам-функциям, если очень хочется. А может использовать какие-то более сложные гибридные схемы. Вместо одной точки кастомизации теперь их целых три: специализация библиотечного шаблона класса; фабрики форматтеров; пользовательский форматтер. Пользовательский форматтер вообще можно навернуть с какой угодно сложностью и обеспечить еще 100500 точек кастомизации, специфичных для прикладной области. В целом гибкость и юзабельность существенно лучше, чем в текущем подходе со специализациями.


Если можно подсунуть функцию фабрику-форматеров в своё пространство имен, то при желании, можно создать у себя универсальный форматтер для всех типов, который уже будет вызывать специализированные функции форматеры или реализовывать любые другие практики принятые в конкретном проекте.
Re[4]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 30.10.22 11:51
Оценка:
Здравствуйте, PM, Вы писали:

PM>Смотрю код новой библиотеки логирования xtr. В ней чтобы форматировать `std::timespec` по-своему, автор сделал свой тип простым наследованием от стандартной структуры:


Если оставить за скобками то, что его подход с наследованием является очень опасной практикой, автор решает ту же проблему, что и я — у меня в мультимедиа время, это дискретная величина и мне нельзя выводить его с округлением, как это предлагает делать libfmt. А от чего будет наследоваться автор, если ему понадобиться изменить или расширить форматирование int ?
Re[22]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 30.10.22 14:30
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Если можно подсунуть функцию фабрику-форматеров в своё пространство имен, то при желании, можно создать у себя универсальный форматтер для всех типов, который уже будет вызывать специализированные функции форматеры или реализовывать любые другие практики принятые в конкретном проекте.


Но есть нюанс: все это будет работать только для типов, определенных пользователем (и производных от них, наподобие std::vector<MyClass>). Настройка форматирования для встроенных типов по-прежнему остается нерешенной проблемой, даже в этом подходе.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[5]: Cоздание базового шаблона минуя специализацию
От: PM  
Дата: 30.10.22 15:45
Оценка:
Здравствуйте, Videoman, Вы писали:

PM>>Смотрю код новой библиотеки логирования xtr. В ней чтобы форматировать `std::timespec` по-своему, автор сделал свой тип простым наследованием от стандартной структуры:


V>Если оставить за скобками то, что его подход с наследованием является очень опасной практикой, автор решает ту же проблему, что и я — у меня в мультимедиа время, это дискретная величина и мне нельзя выводить его с округлением, как это предлагает делать libfmt. А от чего будет наследоваться автор, если ему понадобиться изменить или расширить форматирование int ?


По-моему, наследование для создания другого типа — вполне безопасная, рабочая альтернатива варианту с strong typedef, который предложил коллега so5team.

Думаю, подавляющее большинство сценариев использования — форматировать пользовательские типы, и это вполне работает. Надеюсь автор fmtlib сможет расширить ее для вашего случая, но в std::format это навряд ли уже случится.
Re: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 02.11.22 13:52
Оценка: 8 (1)
В общем для всех тех, кто активно участвовал в дискуссии, критиковал и помогал решать вопрос с форматированием, наталкивая на правильные решения — возможно будет интересно! Кажется вот возможное решение:
#include <string>
#include <iostream>
#include <typeinfo>
#include <utility>

#include <fmt/core.h>
#include <fmt/chrono.h>
#include <fmt/xchar.h>

namespace my
{
// rg45 forward wrapper

template <typename T>
struct wrapper
{
    T t;

    explicit wrapper(T&& t) : t(std::forward<T>(t)) {}

    T&& forward() { return std::forward<T>(t); }
};

template <typename T>
wrapper<T> wrap(T&& t) { return wrapper<T>(std::forward<T>(t)); }



// own format wrapper

template<typename... Params>
std::string format(const std::string_view& format_string, Params&& ...params)
{
    return fmt::format(format_string, wrap(params)...);
}

} // namespace my



// dispatch formatter

template<typename char_t, typename type_t>
class fmt::formatter<my::wrapper<type_t>, char_t>
{
public:
    constexpr auto parse(basic_format_parse_context<char_t>& ctx) -> decltype(ctx.begin())
    {
        auto it = ctx.begin();
        auto end = ctx.end();

        it = std::find(it, end, '}');

        if (it != end && *it != '}')
            throw format_error("invalid format");

        return it;
    }

    template<typename it_t>
    auto format(my::wrapper<type_t>& data, basic_format_context<it_t, char_t>& ctx) const -> decltype(ctx.out())
    {
        return format_to(ctx.out(), "***{}***", data.forward());
    }
};



// test

int main()
{
    std::cout << my::format("{} | {} | {:.e}", 1, "str", 2.5);
}

***1*** | ***str*** | ***2.5***


Это как идея и код для подумать. Думаю тут всё понятно. Можно из форматера дергать свои функции с нужной себе логикой, кастомизировать стандартные и примитивные типы, вызывать std::форматтеры, если надо и т.д.
Хорошо бы проверить будет ли такое работать с std::format C++20.
Re[2]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 02.11.22 14:17
Оценка: +1
Здравствуйте, Videoman, Вы писали:

V>
V>template<typename... Params>
V>std::string format(const std::string_view& format_string, Params&& ...params)
V>{
V>    return fmt::format(format_string, wrap(params)...);
V>}
V>



    return fmt::format(format_string, wrap(std::forward<Params>(params))...);



При вызове wrap разве не нужно форвардить? Если не форвардить, то все params всегда будут интерпретироваться как lvalue выражения и врапиться по lvalue ссылкам. Если так и было задумано, то с форвардингом можно было вообще не заморачиваться, просто биндить всегда по константрой lvalue ссылке.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 02.11.22 14:21
Оценка:
Здравствуйте, rg45, Вы писали:

R>При вызове wrap разве не нужно форвардить? Если не форвардить, то все params всегда будут интерпретироваться как lvalue выражения и врапиться по lvalue ссылкам. Если так и было задумано, то с форвардингом можно было вообще не заморачиваться, просто биндить всегда по константрой lvalue ссылке.


Нужно конечно! Это я пропустил, спешил проверить идею .
Re[2]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 02.11.22 20:57
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Это как идея и код для подумать. Думаю тут всё понятно. Можно из форматера дергать свои функции с нужной себе логикой, кастомизировать стандартные и примитивные типы, вызывать std::форматтеры, если надо и т.д.


То есть, мы враппим std:/fmt:: format и дальше пользуемся только своим my::format. А formatter специализируем только один раз и пишем его так, что он делегирует вызовы пользовательским функциям с использованием ADL. Пожалуй, это будет работать, причем, даже для встроенных типов. Только для встроенных типов нужно будет заимплементить соответствующие функции в глобальном пространстве имен, причем так, чтоб их объявления (как минимум) были видны из нашей единственной специализации форматтера. Правильно я понял идею?

Один нюанс: необходимость определения функций в глобальном пространстве имен для встроенных типов будет диктовать выбор каких-то достаточно оригинальных имен для этих функций, чтобы исключить возможность нечаянных коллизий. Другой способ решения этой проблемы: добавление какого-нибудь фиктивного параметра, имеющего тип, определенный в пространстве имен my. При таком подходе имена для функций можно будет выбрать максимально простые и комфортные и перенести определения самих функций из глобального пространства имен в пространство имен my (это только для встроенных типов — для пользовательских типов функции можно будет спокойно определять в том же пространстве имен, что и сами типы).

V>Хорошо бы проверить будет ли такое работать с std::format C++20.


Ну а почему нет, подходы же одинаковые.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 02.11.2022 21:07 rg45 . Предыдущая версия . Еще …
Отредактировано 02.11.2022 21:01 rg45 . Предыдущая версия .
Re[3]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 03.11.22 09:14
Оценка:
Здравствуйте, rg45, Вы писали:

R>То есть, мы враппим std:/fmt:: format и дальше пользуемся только своим my::format. А formatter специализируем только один раз и пишем его так, что он делегирует вызовы пользовательским функциям с использованием ADL. Пожалуй, это будет работать, причем, даже для встроенных типов. Только для встроенных типов нужно будет заимплементить соответствующие функции в глобальном пространстве имен, причем так, чтоб их объявления (как минимум) были видны из нашей единственной специализации форматтера. Правильно я понял идею?


Не совсем. Всё так, но не понятно зачем в глобальном пространстве, когда можно в локальном. Класс диспетчера-то наш и мы вольны вызывать что хотим.

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


Опять не понимаю. Зачем это делать в глобальном пространстве, можно ведь:
// dispatch formatter

template<typename char_t, typename type_t>
class fmt::formatter<my::wrapper<type_t>, char_t>
{
public:
    constexpr auto parse(basic_format_parse_context<char_t>& ctx) -> decltype(ctx.begin())
    {
        return my::parse(...);
    }

    template<typename it_t>
    auto format(my::wrapper<type_t>& data, basic_format_context<it_t, char_t>& ctx) const -> decltype(ctx.out())
    {
        return my::format(...);
    }
};

// or 

template<typename char_t, typename type_t>
class fmt::formatter<my::wrapper<type_t>, char_t>
{
    my::formatter<type_t, char_t> m_formatter;

public:
    constexpr auto parse(basic_format_parse_context<char_t>& ctx) -> decltype(ctx.begin())
    {
        return m_formatter.parse(...);
    }

    template<typename it_t>
    auto format(my::wrapper<type_t>& data, basic_format_context<it_t, char_t>& ctx) const -> decltype(ctx.out())
    {
        return m_formatter.format(...);
    }
};
и пустить свои иерархии объектов или функций в своём пространстве имен. Я не описываю делали сейчас. Просто указываю на точку кастомизации, из которой всё можно перенести в свой неймспейс и там уже делать кастомизацию или вызывать дефолтные реализации std, если нужно.
Re[4]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 03.11.22 11:28
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Не совсем. Всё так, но не понятно зачем в глобальном пространстве, когда можно в локальном. Класс диспетчера-то наш и мы вольны вызывать что хотим.


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

V>Опять не понимаю. Зачем это делать в глобальном пространстве, можно ведь:[ccode]

V>
V>        return my::parse(...);
V>        return my::format(...);
V>


Ну и то, что мы получим, будет не многим лучше того, что мы имеем. ADL при таких вызвах не задествуется. И мы снова заставляем пользователя рвать свои пространства имен для того, чтобы определить функции parse и format в пространстве имен my. Ради этого овчинка выделки не стоит, имхо.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.11.2022 12:12 rg45 . Предыдущая версия . Еще …
Отредактировано 03.11.2022 12:09 rg45 . Предыдущая версия .
Отредактировано 03.11.2022 11:33 rg45 . Предыдущая версия .
Отредактировано 03.11.2022 11:32 rg45 . Предыдущая версия .
Re[5]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 03.11.22 12:18
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну и то, что мы получим, будет не многим лучше того, что мы имеем — мы снова заставим пользователя рвать свои пространства имен для того, чтобы определить функции parse и format в пространстве имен my. Ради такого результата овчинка выделки не стоит, имхо.


Я описал просто точку кастомизации. Из нее можно подлезть и сделать как угодно. Я не повторял детали, которые мы до этого тут обсуждали. Можно же сделать и так:
parse(...., my::parse_as<type_t>());
format(...., my::format_as<type_t>());

Тогда ADL начнет работать по полной. Если дашь конкретику чего хочется получить и время, то я постараюсь показать полный код. Или я опять не понял вопроса?
Re[6]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 03.11.22 12:33
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Я описал просто точку кастомизации. Из нее можно подлезть и сделать как угодно.


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

V>Я не повторял детали, которые мы до этого тут обсуждали. Можно же сделать и так:

V>parse(...., my::parse_as<type_t>());
V>format(...., my::format_as<type_t>());

Ну вот этот вариант близок к тому, о чем я говорил здесь
Автор: rg45
Дата: 02.11.22
: использование фейкового параметра при неквалифицированном вызове заставит компилятор заглянут в пространство имен my и найти там функции обработки встроенных типов (а также для типов из сторонних библиотек и std:). Для UDT же процедуры обработки удобнее всего определить в тех же пространствах имен, что и сами типы, наверное, хотя чисто технически можно и в пространстве имен my — компилятор заглянет и туда, и туда, при таком подходе.

V>Тогда ADL начнет работать по полной. Если дашь конкретику чего хочется получить и время, то я постараюсь показать полный код. Или я опять не понял вопроса?


Ну я уже запал на вариант с фабрикой
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 03.11.2022 12:53 rg45 . Предыдущая версия .
Re[7]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 11.11.22 11:42
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну я уже запал на вариант с фабрикой


Тут натолкнулся на проблему с подходом со враппером и не char-строками. Не могу понять что fmt не нравится и почему не находится перегрузка. Толи c wrapper-ом что-то не то, толи ещё что-то. Кстати с wrapper-ом точно что-то не то, т.к. T&& у wrapper(T&& t) не является универсальной ссылкой. Наверное должно быть так:
template<typename U>
explicit wrapper(U&& t) : t(std::forward<U>(t)) {}
Еще есть вариант что библиотека fmt обрабатывает стандартные типы внутри себя, без использования fmt::formatters, и из-за этого вся последовательность логики ломается.
Re[8]: Cоздание базового шаблона минуя специализацию
От: vopl Россия  
Дата: 11.11.22 12:30
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, rg45, Вы писали:


R>>Ну я уже запал на вариант с фабрикой


V>Тут натолкнулся на проблему с подходом со враппером и не char-строками. Не могу понять что fmt не нравится и почему не находится перегрузка. Толи c wrapper-ом что-то не то, толи ещё что-то. Кстати с wrapper-ом точно что-то не то, т.к. T&& у wrapper(T&& t) не является универсальной ссылкой. Наверное должно быть так:
V>template<typename U>
V>explicit wrapper(U&& t) : t(std::forward<U>(t)) {}
V>
Еще есть вариант что библиотека fmt обрабатывает стандартные типы внутри себя, без использования fmt::formatters, и из-за этого вся последовательность логики ломается.


Выглядит как недоработка библиотеки. Смотрю исходники, нет перегрузки format/vformat для широких строк... Судя по всему, твоя ошибка случается потому что по ходу пъесы wchar_t подменяется на char (смотри using format_string), ну и далее от этого все идет в разнос
Re[8]: Cоздание базового шаблона минуя специализацию
От: rg45 СССР  
Дата: 11.11.22 13:02
Оценка: +1
Здравствуйте, Videoman, Вы писали:

V>Кстати с wrapper-ом точно что-то не то, т.к. T&& у wrapper(T&& t) не является универсальной ссылкой. Наверное должно быть так:
V>template<typename U>
V>explicit wrapper(U&& t) : t(std::forward<U>(t)) {}
V>
Еще есть вариант что библиотека fmt обрабатывает стандартные типы внутри себя, без использования fmt::formatters, и из-за этого вся последовательность логики ломается.


Все верно, wrapper<T> не использует механизм perfect forwarding непосредственно, но он перенимает результаты использования perfect forwarding у функции wrap как эстафетную палочку. Если ты будешь создавать объект wrapper<T> непосредсвенно, минуя функцию wrap, то T — это тот тип, которым ты параметризовал шаблонный класс wrapper, и ни о каких perfect forwarding ссылках, конечно же, не может быть и речи при таком использовании. Если же ты создаешь объект wrapper<T> через функцию wrap, то T — это тип, выведенный при матчинге настоящей perfect forwarding ссылки, являющейся параметром функции wrap. В этом случае Т — это может быть как тип объекта, так и lvalue сылка на объект и результат будет ровно таким же, как если бы функция возвращала бы результат напрямую, без враппинга — T wrap(T&&):

http://coliru.stacked-crooked.com/a/4bf7f04d3c1aad5a

#include <iostream>

template <typename T>
struct wrapper
{
    T t;

    explicit wrapper(T&& t) : t(std::forward<T>(t)) {}

    T&& forward() { return std::forward<T>(t); }
};

template <typename T>
wrapper<T> wrap(T&& t) { return wrapper<T>(std::forward<T>(t)); }

int main()
{
    int value = 42;
    
    std::cout << "Value before: " << value << std::endl;

    wrap(value).forward() = 43;

    std::cout << "Value after: " << value << std::endl;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 11.11.2022 13:12 rg45 . Предыдущая версия . Еще …
Отредактировано 11.11.2022 13:09 rg45 . Предыдущая версия .
Re[9]: Cоздание базового шаблона минуя специализацию
От: Videoman Россия https://hts.tv/
Дата: 11.11.22 14:21
Оценка:
Здравствуйте, vopl, Вы писали:

V>Выглядит как недоработка библиотеки. Смотрю исходники, нет перегрузки format/vformat для широких строк... Судя по всему, твоя ошибка случается потому что по ходу пъесы wchar_t подменяется на char (смотри using format_string), ну и далее от этого все идет в разнос


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