#video
Рефлексия в C++
C++ отличается от многих языков отсутствием рефлексии. Рефлексия это способность программы "понимать" свою собственную структуру. Например - получить имя класса, список методов, добавлять методы, и так далее.
Рефлексии в run-time не будет никогда, потому что ~95% информации из исходников после компиляции просто испаряется, и полноценная рефлексия невозможна.
Развитие рефлексии в compile-time тормозилось несовершенством compile-time вычислений, поэтому эта фича войдет не ранее C++26.
О рефлексии рассказывает её разработчик Andrew Sutton - https://youtu.be/60ECEc-URP8
Интересно, что это уже второй подход к реализации рефлексии (на основе constexpr), был еще первый вариант - на основе шаблонов.
На основе этого видео и прочих источников я писал январьскую статью про рефлексию в C++Next, которую можно почитать, если тема покажется интересной 🙂 https://habr.com/ru/post/598981/
Рефлексия в C++
C++ отличается от многих языков отсутствием рефлексии. Рефлексия это способность программы "понимать" свою собственную структуру. Например - получить имя класса, список методов, добавлять методы, и так далее.
Рефлексии в run-time не будет никогда, потому что ~95% информации из исходников после компиляции просто испаряется, и полноценная рефлексия невозможна.
Развитие рефлексии в compile-time тормозилось несовершенством compile-time вычислений, поэтому эта фича войдет не ранее C++26.
О рефлексии рассказывает её разработчик Andrew Sutton - https://youtu.be/60ECEc-URP8
Интересно, что это уже второй подход к реализации рефлексии (на основе constexpr), был еще первый вариант - на основе шаблонов.
На основе этого видео и прочих источников я писал январьскую статью про рефлексию в C++Next, которую можно почитать, если тема покажется интересной 🙂 https://habr.com/ru/post/598981/
YouTube
Reflection: Compile-Time Introspection of C++ - Andrew Sutton [ ACCU 2021 ]
#Programming #Cpp #AccuConf
Slides: https://accu.org/conf-previous/2021/schedule/
ACCU Website: https://www.accu.org
ACCU Conference Website: https://conference.accu.org
ACCU Twitter: @ACCUConf
Streamed & Edited By Digital Medium Ltd: https://events.digital…
Slides: https://accu.org/conf-previous/2021/schedule/
ACCU Website: https://www.accu.org
ACCU Conference Website: https://conference.accu.org
ACCU Twitter: @ACCUConf
Streamed & Edited By Digital Medium Ltd: https://events.digital…
C++95
(text below)
#compiler
Как пропатчить Clangпод FreeBSD
На прошлой неделе я решил попробовать починить что-нибудь в Clang, чтобы посмотреть, как это делается. Я нашел на гитхабе несколько issue, связанных с
Всего я сделал четыре патча (каждый не более нескольких строк), их можно увидеть на скриншоте
(второй снизу патч - "пробный шар" в
Один из них за неделю успели апрувнуть, другие пока висят ибудут висеть много лет их судьба неясна. Какие есть впечатления и общие факты:
(1) Открытые гитхаб-issue реально могут висеть годами, пока их кто-нибудь не починит. Этот срок, наверное, коррелирует с важностью бага, но какого-то KPI на уменьшение тикетов Clang/LLVM не держит.
(2) Официальное правило из гайда - когда отправляешь патч, ищи ревьюеров сам. По блейму, похожим тикетам, и так далее. Иногда в ревью может прилететь "волшебник на голубом вертолете", но если никто так и не отревьюил, то можно почувствовать себя в шкуре детектива по поиску людей.
(3) Ревьюеры (в моих патчах отписалось 5-6 людей) - ОЧЕНЬ опытные в C++. Погрепав их имена, можно обнаружить их среди авторов многочисленных пропозалов в стандарт, докладчиков CppCon, членов комитета по плюсам, и так далее.
(4) Тусовка контрибьюторов довольно невелика, везде ревью и обсуждения проводят по сути одни и те же люди. Если искать по какому-то конкретному направлению (например
В целом, результат оказался немного хуже, чем я ожидал. Вещи происходят довольно неспешно, что не может не огорчать. Но я в любом случае уважаю всех контрибьюторов, которые в свой unpaid time делают мир немного лучше.
Как пропатчить Clang
consteval
-методами, и по вечерам отправлял на ревью микрофиксы.Всего я сделал четыре патча (каждый не более нескольких строк), их можно увидеть на скриншоте
(второй снизу патч - "пробный шар" в
clang-tidy
, он не считается)Один из них за неделю успели апрувнуть, другие пока висят и
(1) Открытые гитхаб-issue реально могут висеть годами, пока их кто-нибудь не починит. Этот срок, наверное, коррелирует с важностью бага, но какого-то KPI на уменьшение тикетов Clang/LLVM не держит.
(2) Официальное правило из гайда - когда отправляешь патч, ищи ревьюеров сам. По блейму, похожим тикетам, и так далее. Иногда в ревью может прилететь "волшебник на голубом вертолете", но если никто так и не отревьюил, то можно почувствовать себя в шкуре детектива по поиску людей.
(3) Ревьюеры (в моих патчах отписалось 5-6 людей) - ОЧЕНЬ опытные в C++. Погрепав их имена, можно обнаружить их среди авторов многочисленных пропозалов в стандарт, докладчиков CppCon, членов комитета по плюсам, и так далее.
(4) Тусовка контрибьюторов довольно невелика, везде ревью и обсуждения проводят по сути одни и те же люди. Если искать по какому-то конкретному направлению (например
consteval
), то это вообще ~2 человека, которые запиливали эту фичу в соло и кроме них никто посмотреть ревью не сможет.В целом, результат оказался немного хуже, чем я ожидал. Вещи происходят довольно неспешно, что не может не огорчать. Но я в любом случае уважаю всех контрибьюторов, которые в свой unpaid time делают мир немного лучше.
#creepy
Reference Lifetime Extension
Сегодняшнее "стрёмное правило стандарта": если вы инициализируете константную ссылку (
Пример в двух строках:
Наверное, самое популярное использование этого правила - дефолтные значения ссылочных аргументов
Это правило супер легко сломать - как только вызовете метод у временного объекта (
Abseil Tip of the Week показывает больше примеров успеха и фейла.
В моем примере наличие этого правила допустило лютый баг:
Я сделал неправильный каст, который скопировал объект, а не скастил к базовому классу. Правильный каст -
Выстрел в ногу произошел, когда
Reference Lifetime Extension
Сегодняшнее "стрёмное правило стандарта": если вы инициализируете константную ссылку (
const T&
) "временным объектом" (скорее всего rvalue
), то этот временный объект не уничтожается как ему было положено, а продолжает жить ровно столько, сколько живет ссылка.Пример в двух строках:
std::string Foo::GetName();
const std::string& name = obj.GetName(); // легально и не сломается
Наверное, самое популярное использование этого правила - дефолтные значения ссылочных аргументов
void foo(const std::string& s = "default_text");
Это правило супер легко сломать - как только вызовете метод у временного объекта (
obj.GetName().data()
), или если будет сделан неявный каст, и так далее.Abseil Tip of the Week показывает больше примеров успеха и фейла.
В моем примере наличие этого правила допустило лютый баг:
class A { ... };
class B : public A { ... };
void foo(const A& a) { ... }
void foo(const B& b) {
// ...
foo(static_cast<A>(b));
}
Я сделал неправильный каст, который скопировал объект, а не скастил к базовому классу. Правильный каст -
static_cast<const A&>(b)
.Выстрел в ногу произошел, когда
foo(const A& a)
стал сохранять ссылку на a
, чтобы потом её переиспользовать. Пока вызывался foo
, ссылка была рабочей, а вот потом объект разрушился и ссылка стала висячей. Дебаг занял достаточно много времени...#longread
Я давно ничего не писал в этот блог, поэтому решил исправиться 🐸
Сегодня я дописал на habr статью про неклассические контейнеры в C++:
https://habr.com/ru/post/664044/
Из нее вы узнаете о таких контейнерах, как
Я давно ничего не писал в этот блог, поэтому решил исправиться 🐸
Сегодня я дописал на habr статью про неклассические контейнеры в C++:
https://habr.com/ru/post/664044/
Из нее вы узнаете о таких контейнерах, как
static_vector
, small_vector
, dynamic_bitset
и многих других. Рекомендую к прочтению!Хабр
Неклассические контейнеры в C++
Устройство одного из контейнеров, описанного в статье Контейнер - это объект, используемый для хранения других объектов. Контейнер берет на себя управление всей памятью, которые эти объекты занимают....
#creepy
Heterogeneous Lookup
Что будет, если попытаться вызвать
Компаратор это структура, у которой должен быть определен
Проверить, что это именно так, можно через строку с кастомным аллокатором (см. определение std::string), который логирует запрос на аллокации.
Но зачем создавать
Я не смог найти причину, почему это не сделали поведением по умолчанию. Я порылся в библиотеках с кастомными контейнерами и вижу два подхода к вопросу:
(1) homogenous lookup по умолчанию (как в STL) - пример boost::container::map
(2) heterogeneous lookup по умолчанию - пример хэш таблицы в Abseil (от Google), аналог std::unordered_map/set
Перф неплохо улучшается, пример расследования для unordered контейнеров - там цифры от 20% до 35%.
Heterogeneous Lookup
Что будет, если попытаться вызвать
.find()
/.contains()
/etc. у ассоциативных контейнеров (std::set
/std::map
/unordered-версии) с ключом, который не совпадает по типу? 🤔std::map<std::string, int> m;У std::map есть дефолтный компаратор третьим аргументом в шаблоне -
// ...
if (m.contains("foobar")) { ... }
std::less<Key>
. Компаратор нужен для поиска в контейнере.Компаратор это структура, у которой должен быть определен
bool operator()
; выглядит оно примерно так:constexpr bool operator()(const Key &lhs, const Key &rhs) const {То есть тип аргумента должен совпадать с типом ключа. Возвращаясь к примеру в начале:
return lhs < rhs;
}
"foobar"
имеет тип const char[7]
. Он может "разложиться" в тип const char*
, быть преобразован в тип std::string_view
или std::string
. Поэтому, к сожалению, по overload resolution создается временной объект std::string
. 💩Проверить, что это именно так, можно через строку с кастомным аллокатором (см. определение std::string), который логирует запрос на аллокации.
using traceable_string = std::basic_string<char, std::char_traits<char>, trace_allocator<char>>;Проверяемые строки должны быть достаточно длинными, чтобы обойти Small String Optimization и вызвать аллокацию, в моем окружении это от 16 символов - код на godbolt.
Но зачем создавать
std::string
, если разные строковые типы сравниваемы между собой? В C++14 проблему решили лютым костылем 🩼: сделали std::less<> = std::less<void>
и переопределили std::less<void>
кастомно так:template <class Key1, class Key2>Теперь для перфоманса нужно помнить этот хак и делать
constexpr auto operator()(Key1&& lhs, Key2&& rhs) const
return static_cast<Key1&&>(lhs) < static_cast<Key2&&>(rhs);
}
std::map<std::string, int, std::less<>> m;тогда лишних аллокаций не будет - код на godbolt. Это называется громким словом heterogeneous lookup.
Я не смог найти причину, почему это не сделали поведением по умолчанию. Я порылся в библиотеках с кастомными контейнерами и вижу два подхода к вопросу:
(1) homogenous lookup по умолчанию (как в STL) - пример boost::container::map
(2) heterogeneous lookup по умолчанию - пример хэш таблицы в Abseil (от Google), аналог std::unordered_map/set
Перф неплохо улучшается, пример расследования для unordered контейнеров - там цифры от 20% до 35%.
#longread
У
https://habr.com/ru/post/665632/
У
std::unique_ptr
есть минус - он аллоцирует память в куче. А что, если разработать его аналог с памятью на стеке?.. Об этом можно прочитать тут!https://habr.com/ru/post/665632/
Хабр
Концепция умного указателя static_ptr<T> в C++
В C++ есть несколько "умных указателей" - std::unique_ptr , std::shared_ptr , std::weak_ptr . Также есть более нестандартные умные указатели, например в boost 1 : intrusive_ptr , local_shared_ptr . В...
C++95
#longread У std::unique_ptr есть минус - он аллоцирует память в куче. А что, если разработать его аналог с памятью на стеке?.. Об этом можно прочитать тут! https://habr.com/ru/post/665632/
#madskillz
Fast Pimpl
Концепцию из статьи (насколько понял, достаточно необычную) народ принял с трудом, в комментариях было бурное обсуждение и иногда непонимание.
В комментариях затронули тему Fast Pimpl (понятия, не сильно связанного со статьей). Это как раз жутко боянистая концепция.
PImpl используется, чтобы скрыть детали реализации и/или ускорить компиляцию. До его использования код в хидере выглядит так:
То есть вместо самого объекта в структуре лежит буфер памяти под этот объект, и рулят именно им.
В интернете есть много реализаций этой идиомы (по гуглежу "Fast Pimpl"), и есть обёртки, которые хороши собой 🍬
Fast Pimpl
Концепцию из статьи (насколько понял, достаточно необычную) народ принял с трудом, в комментариях было бурное обсуждение и иногда непонимание.
В комментариях затронули тему Fast Pimpl (понятия, не сильно связанного со статьей). Это как раз жутко боянистая концепция.
PImpl используется, чтобы скрыть детали реализации и/или ускорить компиляцию. До его использования код в хидере выглядит так:
#include <third_party/json.hpp>Стандартный подход заключается в замене
struct Value {
third_party::Json data_;
};
T
на std::unique_ptr<T>
, потому что он разрешает использовать incomplete class:namespace third_party { struct Json; }Проблема, которая из-за этого возникает - аллокация объекта в куче, это замедляет рантайм PImpl-ового варианта ⏱ Поэтому используется подход Fast Pimpl:
struct Value {
std::unique_ptr<third_party::Json> data_;
};
struct Value {(вместо
std::aligned_storage<sizeof(T), alignof(T)> data_;
};
sizeof(T)
и alignof(T)
надо руками ввести нужную цифру)То есть вместо самого объекта в структуре лежит буфер памяти под этот объект, и рулят именно им.
В интернете есть много реализаций этой идиомы (по гуглежу "Fast Pimpl"), и есть обёртки, которые хороши собой 🍬
#compiler
extern "C" - на что в действительности это влияет?
Примерно 9 лет назад я пробовал писать игры на C++. Для скриптов я использовал Lua. Он поставлялся в виде статической библиотеки (
В языке C перегрузки методов нет, и в объектный файл попадает "чистое" название метода. В C++ попадает "запутанное", это называется name mangling:
Однако что в остальном? Сам код внутри
Сам Стандарт показывает примеры, из которых видно, что код скомпилируется по правилам C++: [dcl.link] (там есть классы).
Погрепав исходники компилятора, можно точно сказать, что компиляция внутри
Вывод из этого такой: хидеры библиотеки на C должны быть достаточно простыми, чтобы в C и в C++ они компилировались одинаково.
extern "C" - на что в действительности это влияет?
Примерно 9 лет назад я пробовал писать игры на C++. Для скриптов я использовал Lua. Он поставлялся в виде статической библиотеки (
lua.lib
для Windows) и header-файлов. Так как Lua написан на C и lua.lib
собран компилятором C, то чтобы все слинковалось, надо было подключать хидеры так:extern "C" {В то время я не понимал, почему надо делать именно так. Конечно, спустя много времени стало понятно, что дело в перегрузке методов. Перегруженные методы надо как-то отличать между собой.
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
В языке C перегрузки методов нет, и в объектный файл попадает "чистое" название метода. В C++ попадает "запутанное", это называется name mangling:
void hello(int, char) -> hello (в С)
void hello(int, char) -> _Z1helloic (в C++)
extern "C"
нужен как раз для того, чтобы методы и переменные, объявленные внутри него, имели "чистое" имя (код на godbolt)Однако что в остальном? Сам код внутри
extern "C"
компилируется как C++ или как C? Мнение "C это подмножество C++" ошибочно: два языка развиваются независимо. В C есть то, чего нет в C++, например VLA.Сам Стандарт показывает примеры, из которых видно, что код скомпилируется по правилам C++: [dcl.link] (там есть классы).
Погрепав исходники компилятора, можно точно сказать, что компиляция внутри
extern "C"
происходит по правилам C++, и аффектится только условие "надо манглить или нет": исходник Clang.Вывод из этого такой: хидеры библиотеки на C должны быть достаточно простыми, чтобы в C и в C++ они компилировались одинаково.
#books
Обзор книги "Advanced C and C++ Compiling" 📚
(можно скачать тут - https://www.tgoop.com/progbook/6109)
Книги по программированию часто разочаровывают. Они подобны пирамиде - большую долю занимает элементарщина и копипаст документации в переводе Гоблина; в середине находятся менее боянистые книги, но их меньше; и на вершине находится что-то действительно интересное.
Эта книга, которую я читал в прошлом году, имеет очень большую редкость.
В ней описаны роли линкера (linker) и загрузчика (loader) и их работа в очень глубоких деталях (а также компилятора (compiler), но в меньшей степени). Есть много картинок, описаний форматов, команд Linux и Windows, и прочего.
Книга подробная вплоть до того, что объясняет, по какой схеме надо правильно версионировать свои динамические библиотеки! 🤯 (понятно, что это очень специфическое занятие)
Я бы рекомендовал для чтения первые несколько глав, а также главы 12 и 13 (для Linux-разработчиков)
Обзор книги "Advanced C and C++ Compiling" 📚
(можно скачать тут - https://www.tgoop.com/progbook/6109)
Книги по программированию часто разочаровывают. Они подобны пирамиде - большую долю занимает элементарщина и копипаст документации в переводе Гоблина; в середине находятся менее боянистые книги, но их меньше; и на вершине находится что-то действительно интересное.
Эта книга, которую я читал в прошлом году, имеет очень большую редкость.
В ней описаны роли линкера (linker) и загрузчика (loader) и их работа в очень глубоких деталях (а также компилятора (compiler), но в меньшей степени). Есть много картинок, описаний форматов, команд Linux и Windows, и прочего.
Книга подробная вплоть до того, что объясняет, по какой схеме надо правильно версионировать свои динамические библиотеки! 🤯 (понятно, что это очень специфическое занятие)
Я бы рекомендовал для чтения первые несколько глав, а также главы 12 и 13 (для Linux-разработчиков)
Telegram
Книги для программистов
Advanced C and C++ Compiling (2014)
Автор: Milan Stevanovic
Количество страниц: 334
Научиться писать код на C / C++ - это только первый шаг. Чтобы быть серьезным программистом, вы должны понимать структуру и назначение двоичных файлов, создаваемых компилятором:…
Автор: Milan Stevanovic
Количество страниц: 334
Научиться писать код на C / C++ - это только первый шаг. Чтобы быть серьезным программистом, вы должны понимать структуру и назначение двоичных файлов, создаваемых компилятором:…
#story
Как готовятся задачи к олимпиадам по программированию? (Часть 1/2)
Наверное, много кто пробовал решать задачки: на codeforces.com, школьных олимпиадах, ACM ICPC, и в других местах. Большинство людей использует C++ для решения.
Я раньше очень увлекался этим занятием (называется "проблемсеттинг"), и даже эпизодически готовил задачи для codeforces и школьных олимпиад (подготовил примерно 15-20 задач).
Это занятие оплачивается достаточно символически. Поэтому в основном этим занимаются студенты - бывшие топовые олимпиадники, которые еще не закончили университет и не нашли работу. Потом им надоедает и их сменяет другое поколение студентов.
Покажу на примере одной задачи, как происходит подготовка: Codeforces Round #704 - E. Почти отказоустойчивая база данных
(Эта задача самая сложная в том раунде, но у нее простое описание)
Как готовятся задачи к олимпиадам по программированию? (Часть 1/2)
Наверное, много кто пробовал решать задачки: на codeforces.com, школьных олимпиадах, ACM ICPC, и в других местах. Большинство людей использует C++ для решения.
Я раньше очень увлекался этим занятием (называется "проблемсеттинг"), и даже эпизодически готовил задачи для codeforces и школьных олимпиад (подготовил примерно 15-20 задач).
Это занятие оплачивается достаточно символически. Поэтому в основном этим занимаются студенты - бывшие топовые олимпиадники, которые еще не закончили университет и не нашли работу. Потом им надоедает и их сменяет другое поколение студентов.
Покажу на примере одной задачи, как происходит подготовка: Codeforces Round #704 - E. Почти отказоустойчивая база данных
(Эта задача самая сложная в том раунде, но у нее простое описание)
C++95
#story Как готовятся задачи к олимпиадам по программированию? (Часть 1/2) Наверное, много кто пробовал решать задачки: на codeforces.com, школьных олимпиадах, ACM ICPC, и в других местах. Большинство людей использует C++ для решения. Я раньше очень увлекался…
Как готовятся задачи к олимпиадам по программированию? (Часть 2/2)
Подготовка задачи ведется на сайте polygon.codeforces.com. Это самая продвинутая платформа, долгое время была практически единственной. Другие платформы (Яндекс.Контест, CodeChef, etc.) со временем потырили у нее фичи - валидацию, встроенную систему контроля версий, и прочее.
Для программок (генераторы, чекеры, etc.) используется библиотека testlib.h.
Процесс подготовки задачи состоит из этих пунктов (могут идти в произвольном порядке, кроме п.1):
(1) Придумывается не-баянистая идея для задачи в краткой формулировке.
(2) Создается художественное описание задачи в LaTeX - пример, это называется "легенда".
(3) Пишутся генераторы тестов, таких программок может быть несколько - пример 1, пример 2. Они могут запускаться с разными параметрами.
(4) Пишется валидатор, он нужен для проверки, что сгенерированные тесты имеют правильный формат и удовлетворяют ограничениям из условия задачи - пример.
(5) Список тестов создается через движок шаблонов Freemarker. Можно написать скрипт, который потом преобразуется в список запусков генераторов - пример из документации. В нашей задаче 100 тестов - список.
(6) Надо написать решение задачи. Есть одно эталонное правильное решение - пример, но надо также выдумать и написать все возможные неправильные решения, чтобы смотреть, смогут ли они пройти все (или почти все) тесты, и скорректировать тесты от этого.
(7) Чекер проверяет решение участника на правильность. На polygon есть дефолтные чекеры для простых задач, но если в задаче может быть несколько правильных ответов, то нужен свой чекер - пример.
Это дело достаточно серьезное, поэтому проводится перекрёстное код-ревью задач вместе с коллегами-проблемсеттерами и вычитка легенды.
Также примерно 5-15 человек разных рейтингов прорешивают задачу, не зная заранее решения. Они могут придумать неожиданное решение попроще, или заслать неправильное решение - все эти кейсы рассматриваются отдельно.
По закону больших чисел, на одну из десятков задач могут происходить факапы такого рода:
(1) Много участников додумались до решения проще авторского и реальная сложность задачи преувеличена
(2) Задача имеет слабые тесты, из-за чего проходит много "плохих" решений.
(3) Авторское решение неправильное.
Первые два типа факапов это еще ничего, а третий тип это полный треш, и на codeforces контест из-за этого просто отменяют (точнее, делают "нерейтинговым", и участвовать в нем нет смысла).
Подготовка задачи ведется на сайте polygon.codeforces.com. Это самая продвинутая платформа, долгое время была практически единственной. Другие платформы (Яндекс.Контест, CodeChef, etc.) со временем потырили у нее фичи - валидацию, встроенную систему контроля версий, и прочее.
Для программок (генераторы, чекеры, etc.) используется библиотека testlib.h.
Процесс подготовки задачи состоит из этих пунктов (могут идти в произвольном порядке, кроме п.1):
(1) Придумывается не-баянистая идея для задачи в краткой формулировке.
(2) Создается художественное описание задачи в LaTeX - пример, это называется "легенда".
(3) Пишутся генераторы тестов, таких программок может быть несколько - пример 1, пример 2. Они могут запускаться с разными параметрами.
(4) Пишется валидатор, он нужен для проверки, что сгенерированные тесты имеют правильный формат и удовлетворяют ограничениям из условия задачи - пример.
(5) Список тестов создается через движок шаблонов Freemarker. Можно написать скрипт, который потом преобразуется в список запусков генераторов - пример из документации. В нашей задаче 100 тестов - список.
(6) Надо написать решение задачи. Есть одно эталонное правильное решение - пример, но надо также выдумать и написать все возможные неправильные решения, чтобы смотреть, смогут ли они пройти все (или почти все) тесты, и скорректировать тесты от этого.
(7) Чекер проверяет решение участника на правильность. На polygon есть дефолтные чекеры для простых задач, но если в задаче может быть несколько правильных ответов, то нужен свой чекер - пример.
Это дело достаточно серьезное, поэтому проводится перекрёстное код-ревью задач вместе с коллегами-проблемсеттерами и вычитка легенды.
Также примерно 5-15 человек разных рейтингов прорешивают задачу, не зная заранее решения. Они могут придумать неожиданное решение попроще, или заслать неправильное решение - все эти кейсы рассматриваются отдельно.
По закону больших чисел, на одну из десятков задач могут происходить факапы такого рода:
(1) Много участников додумались до решения проще авторского и реальная сложность задачи преувеличена
(2) Задача имеет слабые тесты, из-за чего проходит много "плохих" решений.
(3) Авторское решение неправильное.
Первые два типа факапов это еще ничего, а третий тип это полный треш, и на codeforces контест из-за этого просто отменяют (точнее, делают "нерейтинговым", и участвовать в нем нет смысла).
#creepy и #video
Тупое нововведение в C++23: копирование с помощью auto{}
Что нужно сделать, чтобы скопировать объект, особенно в шаблонном коде? Зная, что объявление переменной с типом
Комментарии к видео интереснее самого видео, все люди ловят фейспалм и пишут комментарии как "в чем вообще проблема копировать как раньше", "почему не сделали в виде
Тупое нововведение в C++23: копирование с помощью auto{}
Что нужно сделать, чтобы скопировать объект, особенно в шаблонном коде? Зная, что объявление переменной с типом
auto
это всегда копия, можно сделать так:void func(const auto& something) {Что, если мы не хотим объявлять новый объект, а создать копию "на месте"? Тогда можно сделать так (decay нужен потому что исходный тип может быть ссылочным и/или иметь cv-квалификаторы):
auto copy = something;
use(copy);
}
void func(const auto& something) {А что, если мы считаем что C++ еще недостаточно сложный? То в С++23 теперь можно делать так:
use(std::decay_t<decltype(something)>{copy});
}
void func(const auto& something) {Эту информацию я узнал из канала "C++ Weekly": https://www.youtube.com/watch?v=5zVQ50LEnuQ В видео приводится не очень убедительный "мотивационный пример".
use(auto{copy});
}
Комментарии к видео интереснее самого видео, все люди ловят фейспалм и пишут комментарии как "в чем вообще проблема копировать как раньше", "почему не сделали в виде
std::copy
", и прочее. В этом я с ними согласен!YouTube
C++ Weekly - Ep 323 - C++23's auto{} and auto()
☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟
T-SHIRTS AVAILABLE!
► The best C++ T-Shirts anywhere! https://my-store-d16a2f.creator-spring.com/
WANT MORE JASON?
► My Training Classes: http://emptycrate.com/training.html
► Follow me on twitter: https:…
T-SHIRTS AVAILABLE!
► The best C++ T-Shirts anywhere! https://my-store-d16a2f.creator-spring.com/
WANT MORE JASON?
► My Training Classes: http://emptycrate.com/training.html
► Follow me on twitter: https:…
#books
Обзор книги "API Design for C++ " 📚
(можно скачать тут - https://www.tgoop.com/progbook/3214)
Эту книгу мне когда-то посоветовал тимлид соседней команды. Он от нее был в полном восторге 🤩 Однако у меня впечатления были более сдержанными 🤔
Что такое API? По определению из книги:
В книге довольно четко можно разделить "общие принципы дизайна API" и "специфические вопросы дизайна API на C++".
Первая составляющая, как философская, действительно очень хороша - есть много толковых идей, подсказок, и разбор API популярных проектов. Книга поможет понять, над какими вопросами думает Software Architect, чтобы API не развалился через несколько недель 👍
Вторая составляющая, как техническая, на мой взгляд, слабовата. Вот некоторые из минусов, которые мне не понравились:
☹️ Книга издана в 2011 году, в ней описываются фичи
☹️ Некоторые параграфы писал Капитан Очевидность, например
☹️ Про статические и динамические библиотеки отведены последние 10 страниц из 450-страничной книги, тоже с тривиальностями.
Обзор книги "API Design for C++ " 📚
(можно скачать тут - https://www.tgoop.com/progbook/3214)
Эту книгу мне когда-то посоветовал тимлид соседней команды. Он от нее был в полном восторге 🤩 Однако у меня впечатления были более сдержанными 🤔
Что такое API? По определению из книги:
An API is a logical interface to a software component that hides the internal details required to implement it.API окружают нас везде, даже внутри одной программы их несколько. И вот книга рассматривает вопросы дизайна API на C++.
В книге довольно четко можно разделить "общие принципы дизайна API" и "специфические вопросы дизайна API на C++".
Первая составляющая, как философская, действительно очень хороша - есть много толковых идей, подсказок, и разбор API популярных проектов. Книга поможет понять, над какими вопросами думает Software Architect, чтобы API не развалился через несколько недель 👍
Вторая составляющая, как техническая, на мой взгляд, слабовата. Вот некоторые из минусов, которые мне не понравились:
☹️ Книга издана в 2011 году, в ней описываются фичи
C++0x
(рабочее название стандарта C++11
), но какой-то древней редакции. Нет ни слова про auto
, руками пишутся std::vector<double>::iterator
, и т.д.☹️ Некоторые параграфы писал Капитан Очевидность, например
Avoid #define for constants
☹️ На действительно очень важный вопрос совместимости ABI отведена буквально 1 страница с тривиальностями. Без упоминания тулзов как abidiff и каких-то специфических советов.☹️ Про статические и динамические библиотеки отведены последние 10 страниц из 450-страничной книги, тоже с тривиальностями.
Telegram
Книги для программистов
API Design for C++ (2011)
Автор: Martin Reddy
#cpp #book #english
Язык: английский.
Целевая аудитория: опытные разработчики на С++.
Если вы уже прошли курс начинающего разработчика на С++, вам, вероятно, захочется попробовать свои силы и сделать на…
Автор: Martin Reddy
#cpp #book #english
Язык: английский.
Целевая аудитория: опытные разработчики на С++.
Если вы уже прошли курс начинающего разработчика на С++, вам, вероятно, захочется попробовать свои силы и сделать на…
#video
Марсианские ужасы 👽
Как программист я часто имею дело с "воздушными замками": пишу код который работает с абстрактными в вакууме понятиями.
Неожиданно я наткнулся на видео, где разбирают софт для марсохода NASA (nasa/fprime). Сразу же, задыхаясь от интереса, открыл видео и принялся смотреть. Настоящей глыбой является мегадевайс, для которого писался этот код.
Первые 15 минут видео составляет унылое обозрение CI и велопарка .bash-скриптов для билда. Оттуда мы узнаем, что по авторитетной оценке анализатора lgtm.com репозиторий является редкой годнотой с качеством
Одухотворившись этими фактами, я превознесся в ожидании обзора кода, достаточно качественного для поиска марсианской цивилизации рептилоидов. Однако со своих небес я грохнулся как Стас Барецкий с лестницы.
В полном ужасе я обнаружил обзор на ядреную дедову смесь C89 и C++11, а от количества code smell у меня отклеились обои. Я стал свидетелем такого преступного программирования как:
Свои кривые реализации строки и вектора (буфера)
Константы задаются через
Комментарии к видео интереснее самого видео - работники разных European Space Agency подтверждают экстремистский уровень программирования в "научной" и "государственной" сфере.
Марсианские ужасы 👽
Как программист я часто имею дело с "воздушными замками": пишу код который работает с абстрактными в вакууме понятиями.
Неожиданно я наткнулся на видео, где разбирают софт для марсохода NASA (nasa/fprime). Сразу же, задыхаясь от интереса, открыл видео и принялся смотреть. Настоящей глыбой является мегадевайс, для которого писался этот код.
Первые 15 минут видео составляет унылое обозрение CI и велопарка .bash-скриптов для билда. Оттуда мы узнаем, что по авторитетной оценке анализатора lgtm.com репозиторий является редкой годнотой с качеством
C/C++
кода A+
.Одухотворившись этими фактами, я превознесся в ожидании обзора кода, достаточно качественного для поиска марсианской цивилизации рептилоидов. Однако со своих небес я грохнулся как Стас Барецкий с лестницы.
В полном ужасе я обнаружил обзор на ядреную дедову смесь C89 и C++11, а от количества code smell у меня отклеились обои. Я стал свидетелем такого преступного программирования как:
Свои кривые реализации строки и вектора (буфера)
ObjBase::~ObjBase() {} // не делают ~ObjBase() = defaultВ коде использование
void resetSer(void);
ExternalSerializeBuffer(ExternalSerializeBuffer& other);
ExternalSerializeBuffer(ExternalSerializeBuffer* other);
const char* operator=(const char* src);
(void)snprintf(buffer, size, ...
reinterpret_cast
в 169 местахКонстанты задаются через
enum
Я думаю, надо сделать аудит NASA на предмет коррупционного сговора с lgtm.com о качестве кода. А также можно не бояться восстания машин, потому что Терминатор скорее будет страдать деменцией и стрелять себе в ноги, чем в Сару Коннор.Комментарии к видео интереснее самого видео - работники разных European Space Agency подтверждают экстремистский уровень программирования в "научной" и "государственной" сфере.
YouTube
C++ Weekly - Exploring and Reviewing F', The Mars Ingenuity Flight Software
☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟
T-SHIRTS AVAILABLE!
► The best C++ T-Shirts anywhere! https://my-store-d16a2f.creator-spring.com/
WANT MORE JASON?
► My Training Classes: http://emptycrate.com/training.html
► Follow me on twitter: https:…
T-SHIRTS AVAILABLE!
► The best C++ T-Shirts anywhere! https://my-store-d16a2f.creator-spring.com/
WANT MORE JASON?
► My Training Classes: http://emptycrate.com/training.html
► Follow me on twitter: https:…
#advice
Избыточный const снижает перфоманс ⏱
Правильное использование
Пусть у нас есть структура, которая представляет собой API-объект. Содержимое этой структуры не планируется как-то изменять после создания, поэтому с первого взгляда логично, чтобы все поля были объявлены константными:
Дело в том, что
(пример на godbolt с логами)
Также константность полей часто просто не нужна - для нашей цели достаточно, чтобы константным был сам объект (или ссылка на него), а не его поля.
Избыточный const снижает перфоманс ⏱
Правильное использование
const
это большая тема в C++. Самое крутое объяснение я видел на Const Correctness, C++ FAQ, но там не показан один из минусов "избыточного" const
.Пусть у нас есть структура, которая представляет собой API-объект. Содержимое этой структуры не планируется как-то изменять после создания, поэтому с первого взгляда логично, чтобы все поля были объявлены константными:
struct Widget {Но это плохо, если в коде объекты этого типа перемещаются, хранятся в векторе, и так далее.
const std::size_t radius;
const std::vector<Event> events;
const Element element;
};
Дело в том, что
const
-поля нельзя мувнуть, поэтому в записи Widget w2{std::move(w1)}
поля events
и element
скопируются.(пример на godbolt с логами)
Также константность полей часто просто не нужна - для нашей цели достаточно, чтобы константным был сам объект (или ссылка на него), а не его поля.
void Do(const Widget& widget);
#madskillz и #video
Успех применения идиомы Read-Copy-Update в C++
В видео За полгода - C++ Погода разработчик Яндекс.Погоды Дмитрий Старков рассказывает о переезде погодных микросервисов на C++ 🌤
До переезда Погода разрабатывалась на Node.JS + Memcached и отвечала за 300мс в 99 перцентиле. Это было медленно даже с кэшем, а после улучшения погодных предсказаний кэш стал сильно разъезжаться с реальными данными 👎🏻
Чтобы ускориться и улучшить утилизацию CPU, решили переписать на C++.
Для быстроты данные держатся in-memory, не в СУБД.
В памяти программа держит погодное состояние, которое регулярно читается (по GET-запросам от пользователей) и обновляется (по POST-запросам от поставщиков данных).
Обычный подход - shared_ptr:
Более быстрый подход - использование
Однако такой подход все равно был недостаточно быстрым (в видео есть графики со "спайками"). Поэтому применили lock-free идиому Read-Copy-Update. Состояние живет тут:
В видео есть описание работающей хитрой схемы, я пересмотрел часть с кодом несколько раз, чтобы полностью понять его.
Переписывание обернулось успехом - при возросшем (за время переписывания) в 1.5 раза RPS Погода отказалась от рассинхронизирующих кэшей и стала отвечать за 30мс на 99 перцентиле! ⏱
Успех применения идиомы Read-Copy-Update в C++
В видео За полгода - C++ Погода разработчик Яндекс.Погоды Дмитрий Старков рассказывает о переезде погодных микросервисов на C++ 🌤
До переезда Погода разрабатывалась на Node.JS + Memcached и отвечала за 300мс в 99 перцентиле. Это было медленно даже с кэшем, а после улучшения погодных предсказаний кэш стал сильно разъезжаться с реальными данными 👎🏻
Чтобы ускориться и улучшить утилизацию CPU, решили переписать на C++.
Для быстроты данные держатся in-memory, не в СУБД.
В памяти программа держит погодное состояние, которое регулярно читается (по GET-запросам от пользователей) и обновляется (по POST-запросам от поставщиков данных).
Обычный подход - shared_ptr:
std::shared_ptr<State> state;И использование
std::mutex
с std::lock_guard
для безопасного чтения/записи.Более быстрый подход - использование
std::shared_mutex
и std::shared_lock
(для читателей) и std::unique_lock
(для писателей)Однако такой подход все равно был недостаточно быстрым (в видео есть графики со "спайками"). Поэтому применили lock-free идиому Read-Copy-Update. Состояние живет тут:
std::atomic<std::shared_ptr<State>> state;И читатель атомарно копирует объект, лежащий внутри
std::atomic
, прежде чем как-то с ним работать. Писатель также атомарно изменяет объект.В видео есть описание работающей хитрой схемы, я пересмотрел часть с кодом несколько раз, чтобы полностью понять его.
Переписывание обернулось успехом - при возросшем (за время переписывания) в 1.5 раза RPS Погода отказалась от рассинхронизирующих кэшей и стала отвечать за 30мс на 99 перцентиле! ⏱
YouTube
C++ Zero Cost Conf 31 июля 2021. 1 трек.
С++ Zero Cost Conf — новая конференция, которую разработчики на C++ в Яндексе провели для всех практикующих плюсовиков. Прикладной С++ — измеримые метрики и реальные кейсы. Конференция идёт в два потока.
Телеграмм чат конференции (https://www.tgoop.com/Cxx_Zero_Cost_Conf).…
Телеграмм чат конференции (https://www.tgoop.com/Cxx_Zero_Cost_Conf).…
#creepy
Двуликий inline для функций
Так вышло, что сейчас в C++ ключевое слово
(1) [dcl.inline].2: Подсказка компилятору о том, что более предпочтительна подстановка кода из функции в место вызова, чем сам вызов функции. (В компиляторе Clang в LLVM IR этот атрибут у функции называется
(2) [dcl.inline].6: Правило, что у функции может быть несколько определений (definition) за программу. Для отсутствия UB это должны быть одинаковые определения - что естественным образом соблюдается, т.к. обычно метод определен в хидере. (В компиляторе атрибут у функции называется
Получается, что у функции есть два атрибута -
Прикол в том, что:
Стандарт написан так, что
Clang выполняет именно это, т.е.
То есть получается вот что:
А вот методы, которые по Стандарту являются implicit inline functions, например:
(*) инстанциации шаблонов
(*) default и deleted члены классов, и их неявные методы
(*) методы, чей
(*)
... они только
Если бы я мог редактировать стандарт, я бы переименовал inline functions в linkonce functions, потому что текущее описание в Стандарте сильно запутывает...
Интересные вопросы:
(1) Так ли сильно влияет наличие атрибута
Влияет практически незаметно. Порог для инлайнинга у обычного метода 225 попугаев, для inlinehint-метода 325 попугаев (ссылка на код), а для реальной разницы нужно ~1000 попугаев.
То есть в 99% кейсов
(2) Почему один атрибут вытекает из другого, если они ортогональны?
Чтобы компилятор мог в translation unit заинлайнить (
Двуликий inline для функций
Так вышло, что сейчас в C++ ключевое слово
inline
для функций (точнее, само понятие inline
-функции) наделяет их двумя абсолютно ортогональными друг другу effective смыслами. Понятно, что один смысл вытек из другого, но все равно это раздельные сущности. Это:(1) [dcl.inline].2: Подсказка компилятору о том, что более предпочтительна подстановка кода из функции в место вызова, чем сам вызов функции. (В компиляторе Clang в LLVM IR этот атрибут у функции называется
inlinehint
)(2) [dcl.inline].6: Правило, что у функции может быть несколько определений (definition) за программу. Для отсутствия UB это должны быть одинаковые определения - что естественным образом соблюдается, т.к. обычно метод определен в хидере. (В компиляторе атрибут у функции называется
linkonce_odr
)Получается, что у функции есть два атрибута -
inlinehint
и linkonce
.Прикол в том, что:
Стандарт написан так, что
inlinehint
подразумевается только у функций, где явно написан спецификатор inline
.Clang выполняет именно это, т.е.
inlinehint
ставится тогда и только тогда, когда есть спецификатор inline
.То есть получается вот что:
inline int random() { return 4; }
будет И inlinehint
, И linkonce
.А вот методы, которые по Стандарту являются implicit inline functions, например:
(*) инстанциации шаблонов
(*) default и deleted члены классов, и их неявные методы
(*) методы, чей
definition
находится внутри definition
класса(*)
constexpr
- и consteval
-функции (на самом деле только constexpr
, т.к. consteval
"испаряется" и в LLVM IR его код тупо не попадает)... они только
linkonce
. Надо все равно писать ключевое слово inline
, чтобы было linkonce
+inlinehint
.Если бы я мог редактировать стандарт, я бы переименовал inline functions в linkonce functions, потому что текущее описание в Стандарте сильно запутывает...
Интересные вопросы:
(1) Так ли сильно влияет наличие атрибута
inlinehint
на компиляцию? Я слышал, что компиляторы уже давно не обращают внимание на подобные подсказки...Влияет практически незаметно. Порог для инлайнинга у обычного метода 225 попугаев, для inlinehint-метода 325 попугаев (ссылка на код), а для реальной разницы нужно ~1000 попугаев.
То есть в 99% кейсов
inline constexpr void foo()
и constexpr void foo()
будут вести себя одинаково, однако в clang-tidy не принимают патч на удаление redundant inline, потому что остается какой-то непонятный 1% кейсов.(2) Почему один атрибут вытекает из другого, если они ортогональны?
Чтобы компилятор мог в translation unit заинлайнить (
inlinehint
) функцию, TU нужно "видеть" исходник этой функции, то есть её тело. В общем случае это невозможно обеспечить, потому что если у нас N штук TU, то будет N определений одной и той же функции и линкер сломается. Поэтому эта функция должна являться linkonce
, чтобы не нарушился ODR.#compiler
Прикладные linkage types в C++ 🔗
Стандартное добавление функции в C++ происходит так: в
Чтобы определить функцию в
Посмотрим, каким будет LLVM IR (компиляторное промежуточное представление C++-кода) для
У LLVM IR есть интересный список linkage types для символов, типов много. Linkage type определяет, как ведет себя символ во время линковки в бинарник. (Символ - это функция или глобальная переменная)
▪️ У
▪️ У
▪️ У
В программе может быть несколько weak-определений одного и того же символа.
Если все определения символа являются weak, то линковщик берет рандомное определение.
Если какое-то из определений символа является strong (как у
В чем отличие
▪️ В общем случае weak-определения могут быть разными, поэтому компилятор не имеет права заинлайнить вызов weak-функции (после линковки у функции может оказаться совсем другое определение)
▪️ Но Стандарт С++ требует от программиста обеспечить, чтобы inline-функции имели идентичное определение (это естественным образом достигается, если определение находится в
▪️ Поэтому компилятор имеет право заинлайнить вызов метода
Скомпилируем объектный файл
Прикладные linkage types в C++ 🔗
Стандартное добавление функции в C++ происходит так: в
.h
-файле пишется объявление функции (которое попадает в много translation unit), а в одном .cpp
-файле пишется определение функции.Чтобы определить функцию в
.h
-файле, чаще всего пишут спецификатор inline
(это рекомендуемый путь), в несколько раз реже static
.Посмотрим, каким будет LLVM IR (компиляторное промежуточное представление C++-кода) для
int sum1()
, static int sum2()
, inline int sum3()
: ссылка на godbolt.У LLVM IR есть интересный список linkage types для символов, типов много. Linkage type определяет, как ведет себя символ во время линковки в бинарник. (Символ - это функция или глобальная переменная)
▪️ У
sum1
дефолтный linkage type external
- возможно ровно 1 определение символа. Линковщик выдаст ошибку, если в программе будет 0 или >1 определения.▪️ У
sum2
linkage type internal
- символ доступен только из того translation unit, где определен. Если в процессе линковки попадется одноименный символ, то линковщик просто переименует sum2
, чтобы не было коллизии.▪️ У
sum3
linkage type linkonce_odr
. Это типичный weak symbol, как и некоторые другие типы (linkonce
, weak
).В программе может быть несколько weak-определений одного и того же символа.
Если все определения символа являются weak, то линковщик берет рандомное определение.
Если какое-то из определений символа является strong (как у
sum1
), то линковщик берет strong-определение.В чем отличие
linkonce
от linkonce_odr
?▪️ В общем случае weak-определения могут быть разными, поэтому компилятор не имеет права заинлайнить вызов weak-функции (после линковки у функции может оказаться совсем другое определение)
▪️ Но Стандарт С++ требует от программиста обеспечить, чтобы inline-функции имели идентичное определение (это естественным образом достигается, если определение находится в
.h
-файле)▪️ Поэтому компилятор имеет право заинлайнить вызов метода
sum3
- программа от этого сломаться не сможетСкомпилируем объектный файл
> clang++ -c link.cppНа Linux получим объектный файл формата ELF. Используем утилиту
readelf
для чтения таблицы символов. Чтобы вывести исходные имена методов (не mangled-имена), используем утилиту c++filt
:> readelf -s link.o | c++filtПолучим такой вывод (опустил другие символы):
Symbol table '.symtab' contains 21 entries:В общем случае выгоднее использовать inline-методы, чем static-методы! Потому что так меньше загрязняется бинарник, и все статические переменные внутри функции живут в количестве 1 штуки, а не в N штук:
Num: Value Size Type Bind Vis Ndx Name
6: 00000000000000c0 18 FUNC LOCAL DEFAULT 2 sum2(int, int)
14: 0000000000000000 18 FUNC GLOBAL DEFAULT 2 sum1(int, int)
20: 0000000000000000 18 FUNC WEAK DEFAULT 7 sum3(int, int)
inline int* get_address() {
// `dummy` займет sizeof(int) памяти, а со static-методом было бы N*sizeof(int)
static int dummy;
// inline-метод - возвращает один и тот же указатель
// static-метод - уникальный для каждого translation unit
return &dummy;
}
#madskillz
Обертка над потоком вывода 🌊
У каждой крупной компании (и в FAANG) есть своя реализации
Много где, прежде чем вывести немного видоизмененную строку в поток вывода (это объект с оператором
Попробуем сделать по аналогии с std::boolalpha "флаг" для "бесплатного" вывода строки. Мы хотим, чтобы можно было писать так:
Пусть выражение
Для удобства будем работать со всеми типами потоков. Объект
Также можно сделать так, чтобы
По ссылке на godbolt можно посмотреть, как я это сделал.
Другой подход к "бесплатному" выводу строки в нужном формате можно посмотреть в библиотеке abseil - Writing to Stream. В нем потоку отдается "легкий" объект:
Обертка над потоком вывода 🌊
У каждой крупной компании (и в FAANG) есть своя реализации
std::string
и/или разных строковых утилит.Много где, прежде чем вывести немного видоизмененную строку в поток вывода (это объект с оператором
<<
, например std::cout
, std::stringstream
), создается новая строка:log << "Value is " << valueStr.Quote();
Метод Quote()
создаст новую строку с кавычками "
по бокам. Такой код встречается тысячами, и вредит перфомансу ⏱, но это удобнее, чем выводить бесконечные "\""
.Попробуем сделать по аналогии с std::boolalpha "флаг" для "бесплатного" вывода строки. Мы хотим, чтобы можно было писать так:
log << "Value is " << quote << valueStr;
И это было бы аналогично записиlog << "Value is " << '"' << valueStr << '"';
Как это можно сделать?Пусть выражение
((объект потока) << quote)
вернет объект TQuoteWrapper
, а запись ((объект TQuoteWrapper) << str)
вернет исходный поток с записанным туда str
.Для удобства будем работать со всеми типами потоков. Объект
quote
ничего не значит и нужен только для вышеуказанной записи.inline constexpr struct{} quote;
template<typename TStream>
auto operator<<(TStream& stream, decltype(quote)) {
return TQuoteWrapper{stream};
}
Сам объект TQuoteWrapper
имеет оператор <<
, в котором записывает то, что нужно: template<typename TArg>
TStream& operator<<(TArg&& arg) {
return Stream_ << '"' << std::forward<TArg>(arg) << '"';
}
По ссылке на godbolt можно посмотреть, что получилось 👍Также можно сделать так, чтобы
TQuoteWrapper
эскейпил символы строки (например, заменял \"
на \\\"
).По ссылке на godbolt можно посмотреть, как я это сделал.
Другой подход к "бесплатному" выводу строки в нужном формате можно посмотреть в библиотеке abseil - Writing to Stream. В нем потоку отдается "легкий" объект:
std::cout << absl::StreamFormat("\"%s\"", valueStr);
Но есть и свои ограничения - нельзя по-полному перепахать строку (сделать escape символов) через printf-like аргумент