Вы наверно знаете что высший пилотаж он же Высокий Слог С++ это шаблоны и метапрограммирование. Вы обязательно должны нагородить кучу несовместимых типов и самозабвенно искать для них универсальный алгоритм.
Вот очередная статья о том что метапрограммирование и код с шаблонами не такие уж и плохие.
Но я хочу рассказать вам историю одного детективного расследования в недрах крупного OpenBMC-проекта (экосистема серверных платформ swtSyst). Это история о том, как безудержное желание перенести всё в compile-time (constexpr), помноженное на ультрасовременный синтаксис C++20, породило идеальный «молчаливый баг» (silent failure). Он мог бы годами жить в продакшене, успешно компилировался, не выдавал ни одного ворнинга, но полностью ломал логику работы приложения.
Если вы любите метапрограммирование, шаблоны, операторы свёртки (fold expressions) и тонкости работы с памятью в C++ — устраивайтесь поудобнее. Мы отправляемся в шаблонный ад.
Предыстория: как работает подписка в D-Bus
В инфраструктуре OpenBMC общение между демонами (они же сервисы или даже микросервисы, все должно быть максимально модно!) происходит через шину D-Bus. Наше приложение занимается мониторингом «здоровья» железа внутренностей серверов. Чтобы не захлебнуться в терабайтах системных сообщений, приложение использует фильтрацию: на этапе инициализации оно вычисляет наибольший общий префикс пути для всех интересующих нас устройств на D-Bus шине, а затем оформляет подписку на системную шину D-Bus (через механизм path_namespace).
Если приложению например, нужны датчики:
/xyz/openbmc_project/sensors/temperature/cpu/xyz/openbmc_project/sensors/fan/tach
То общий префикс очевиден: /xyz/openbmc_project/sensors/. Шина будет присылать только их, экономя ресурсы CPU.
Шедевр метапрограммирования
Современные программисты уже не могут жить без метапрограммирования, поэтому авторы этого кода решили, что вычислять этот префикс при старте приложения в рантайме — это прошлый век и потеря наносекунд. Было решено перенести всё на этап компиляции.
Для этого они быстренько и не особо напрягаясь сотворили целый стек compile-time конструкций с использованием кортежей, лямбд C++20 с явными шаблонными параметрами и операторов свёртки:
// Список всех поддерживаемых типов элементов в системе using ItemTypeList = std::tuple<SensorItem, FanRedundancyItem, SoftwareControlItem, StorageRootItem, DriveItem, DriveBayItem>; // Магические мета-трансформации: добавляем базовый класс и превращаем в регистры using ItemTypeListEx = utils::tupleAppend<ItemTypeList, InventoryItem>; using Registries = utils::tupleTransform<ItemTypeListEx, ItemRegistry>; // Функция поиска несовпадения двух строк на базе C++20 Ranges constexpr std::string_view commonPrefixOfTwo(std::string_view a, std::string_view b) { auto [first, last] = std::ranges::mismatch(a, b); return {a.begin(), first}; // Запомните эту строчку! } // Оператор свёртки (Fold Expression) для обхода всех типов в пакете параметров template <typename First, typename... Rest> constexpr std::string_view commonPrefixForTypes() { auto result = First::ItemType::itemPathPrefix(); if constexpr (sizeof...(Rest) > 0) { ((result = commonPrefixOfTwo(result, Rest::ItemType::itemPathPrefix())), ...); } return result; } // Главная точка входа, разворачивающая tuple в вариативный пакет template <typename Tuple> constexpr std::string_view findCommonPrefix() { return [&]<std::size_t... Is>(std::index_sequence<Is...>) { return commonPrefixForTypes<std::tuple_element_t<Is, Tuple>...>(); }(std::make_index_sequence<std::tuple_size_v<Tuple>>{}); }
Код выглядит «дорого-богато». Компилятор молча собирал этот шедевр, приложение запускалось и… подписывалось на префикс /xyz/openbmc_project/sensors/.
Появление парадокса
Я думаю изначально все было хорошо, но в один прекрасный момент в систему добавили новый класс StorageRootItem (корень хранилища от swtSyst). И у этого класса префикс пути был жестко задан как:
constexpr auto storageRootPathPrefix = "/com/swtSyst";
И вот я наткнулся на этот шедевр и решил все таки проверить логику зарытую и запутанную в шаблонах, а главное проверить результат ее работы! Мне пришлось воспользоваться базовой логикой и математикой. Функция commonPrefixForTypes берёт префикс первого элемента (SensorItem = /xyz/openbmc_project/sensors/) и начинает последовательно сравнивать его через commonPrefixOfTwo со всеми остальными.
Когда очередь доходит до StorageRootItem, должны сравниться строки:
a="/xyz/openbmc_project/sensors/"b="/com/swtSyst"
Несовпадение происходит на первом же символе после слэша ('x' != 'c'). Функция commonPrefixOfTwo была обязана обрезать строку и вернуть "/" (или пустую строку, если слэши не совпали бы).
Если общий префикс сбрасывается до "/", приложение подписывается на корень всей шины. Но в рантайме логгер упорно продолжал выводить посчитанный префикс таким:
PREF=/xyz/openbmc_project/sensors/
При этом логи обработчиков кричали, что внутрь колбэков каким-то чудом пролезают объекты инвентаря системы с путями /com/swtSyst/mctp/....
Как? Если D-Bus фильтр равен /xyz/..., сообщения из ветки /com/... физически не могут прийти напрямую от шины! (Спойлер: они пролезали «паровозиком» через механизм D-Bus Ассоциаций ObjectMapper-а, но это тема для отдельной статьи).
Главный вопрос был в другом: Почему математика C++ сломалась, и префикс не сбросился до "/"?
Снимаем заклинание constexpr
Пытаться отлаживать constexpr-функции, развернутые через три слоя шаблонов — это занятие для мазохистов. Компилятор либо соглашается с кодом, либо выплевывает терабайты нечитаемого выхлопа шаблонов. Я, естественно, не долго думая скормил эту ошибку и код шаблонов первому попавшемуся универсальному ИИ-агенту и он упорно предлагал мне все новые и новые шаблонные конструкции для всяких проверок и отладки, отладка становилась все страшнее.
Поэтому мной было принято единственно верное инженерное решение: «Хватит с нас constexpr-шенов, наелись!».
Мы (с агентом) моим волевым решением убираем ключевое слово constexpr у всей цепочки функций, превращая compile-time магию в обычный рантайм-код, и вставляем туда пошаговое логирование каждого чиха через lg2 (аналог системного журнала в OpenBMC):
template <typename First, typename... Rest> std::string_view commonPrefixForTypes() // <-- Прощай, constexpr { std::string_view result = First::ItemType::itemPathPrefix(); lg2::info("--- СТАРТ: Начальный префикс: {PFX}", "PFX", result); if constexpr (sizeof...(Rest) > 0) { auto logAndCompare = [&](std::string_view className, std::string_view nextPrefix) { std::string_view oldResult = result; result = commonPrefixOfTwo(result, nextPrefix); lg2::info("--- ШАГ: Сравниваем [{OLD}] с [{NEXT}] (класс: {CLS}) -> Получили: [{RES}]", "OLD", oldResult, "NEXT", nextPrefix, "CLS", className, "RES", result); return 0; }; ((logAndCompare(typeid(typename Rest::ItemType).name(), Rest::ItemType::itemPathPrefix())), ...); } return result; }
Компилируем, заливаем свежий бинарник на железку, запускаем и смотрим на этот потрясающий бред в консоли:
<6> --- СТАРТ: Начальный префикс от первого класса (10SensorItem): /xyz/openbmc_project/sensors/ <6> --- ШАГ: Сравниваем [/xyz/openbmc_project/sensors/] с [/xyz/openbmc_project/control/] -> Получили: [/xyz/openbmc_project/sensors/] <6> --- ШАГ: Сравниваем [/xyz/openbmc_project/sensors/] с [/com/swtSyst] (класс: 15StorageRootItem) -> Получили: [/xyz/openbmc_project/sensors/] <6> --- ИТОГ: Финальный общий префикс для шины: [/xyz/openbmc_project/sensors/]
Агент говорит мне: «Посмотрите на этот шаг внимательно:
Сравниваем [/xyz/openbmc_project/sensors/] с [/com/swtSyst] -> Получили: [/xyz/openbmc_project/sensors/]
Шаблоны отработали честно. Оператор свёртки развернулся идеально. Он последовательно вызвал функцию для всех 7 классов. Но функция сравнения строк сошла с ума.»
Король багов: коварный string_view
Мы локализовали проблему. Виновник — «простая» функция commonPrefixOfTwo. Давайте вчитаемся в неё ещё раз:
constexpr std::string_view commonPrefixOfTwo(std::string_view a, std::string_view b) { auto [first, last] = std::ranges::mismatch(a, b); return {a.begin(), first}; // Вот она, мина замедленного действия! }
Что делает автор? Он находит первый несовпадающий символ. std::ranges::mismatch возвращает пару итераторов. Переменная first — это итератор, указывающий на символ несовпадения (на букву 'x' в строке "/xyz...").
Автор хочет вернуть срез строки от её начала (a.begin()) до места несовпадения (first). Он пишет фигурные скобки: return {a.begin(), first};.
Вектор или классическая строка от двух таких аргументов построили бы диапазон (range) от начала до конца. Но std::string_view устроен иначе!
У std::string_view исторически нет конструктора, принимающего два итератора (begin, end). Его конструктор от двух аргументов принимает (указатель на начало, РАЗМЕР строки), где размер имеет тип size_t.
Что сделал компилятор, когда увидел {a.begin(), first}?
a.begin()— это указатель на начало строки (char*). Тут всё ок.first— это тоже итератор (указатель). Компилятор не нашел подходящего конструктора и применил неявное приведение типов, превратив указательfirstв число типаsize_t.
А что такое указатель, приведенный к числу в 64-битной Linux-системе? Это огромный адрес в оперативной памяти (например, 0x7fff12345678).
В итоге std::string_view подумал, что от него требуют: «Возьми строку "a" и отмерь от её начала кусок длиной в 140 триллионов символов».
Но std::string_view — штука не глупая. Внутри она хранит исходную длину строки a (длину пути сенсоров). Поняв, что у нее просят длину, превышающую длину контента в наличии, она просто защитила себя (и нас) от UB (Undefined Behavior), обрезав этот запрос по своему максимально доступному размеру и… тупо вернув строку a целиком!
Функция commonPrefixOfTwo из-за кривого конструктора превратилась в заглушку, которая всегда возвращала первый аргумент, полностью игнорируя то, с чем его сравнивают.
Автор шаблонов настолько был увлечен шаблонами, что просто не придавал значения обычной логике, которая лежит в основе всего, для которой любое, сколько угодно продвинутое метапрограммирование является, всего лишь, вычурным оформлением в большинстве случаев.
Почему я не стал это чинить
Исправить этот баг — дело одной строки. Нужно просто по-человечески вычислить расстояние (длину) между итераторами через std::distance и взять нормальный подстрочный срез:
constexpr std::string_view commonPrefixOfTwo(std::string_view a, std::string_view b) { auto [first, last] = std::ranges::mismatch(a, b); size_t length = std::distance(a.begin(), first); return a.substr(0, length); }
Если применить этот фикс, функция честно выдаст префикс "/". Проект подпишется на корень шины, и демон мониторинга здоровья «ляжет» под нагрузкой от миллиарда системных сообщений, на обработку которых он не рассчитан. Архитектурно типы инвентаря и сенсоров просто нельзя было мешать в один общий котёл, их нужно было разносить на разные инстансы подписок, но кто же это мог заметить под слоями шаблонного кода?
Конечно я не стал отправлять этот патч в репозиторий, потому что это не единственная ошибка. Это такая ошибка из-за которой все и работает. Ошибка которая компенсирует другие ошибки. И очень здорово что шаблоны, constexpr-ешены так замечательно позволяют маскировать нетривиальные методы выправления других нетривиальных ошибок. (Или нет? Не здорово?)
Кстати, за месяц пока у меня дошли руки до этой статьи этот код уже переписали, но, конечно, слои шаблонов с constexpr-ешенами никто не посмел трогать. Они так и сидят там и затравленно смотрят на всех: «Попробуй тронь меня! Пятнами пойдешь и от тебя все отвернутся!»
Выводы для инженеров
Меньше магии, больше рантайма. Вычисление префикса строки один раз при старте демона занимает наносекунды. Попытка сэкономить их в
constexprчерез тонны шаблонов привела бы возможно к неделям отладки, пока разработчики смогли бы преодолеть благоговейный ужас и попробовать зайти ниже границы шаблонов. Читаемость кода и простота его поддержки всегда важнее.Опасайтесь неявных приведений.
std::string_view— прекрасный инструмент, но отсутствие конструктора(iterator, iterator)в сочетании со всеядными фигурными скобками{}может сыграть злую шутку. То есть при работе с любым более менее сложным инструментом надо обращать внимание на нюансы работы с ним и не надеяться что вы точно знаете-понимаете-помните как он работает. Надо проверять себя.Лонглайв рантайм-логи. Если код ведет себя как черная магия или просто генерирует бред — отключайте
constexpr, выкидывайте шаблоны, пишите старый добрый последовательный код и смотрите в логи. Они никогда не врут.
Но мое мнение совершенно субъективное, вряд ли не него стоит ориентироваться в рамках вашего коллектива. Не отрывайтесь от коллектива!
Комментарии (23)

CitizenOfDreams
29.06.2026 02:34Чтобы не захлебнуться в терабайтах системных сообщений
А что вообще можно сообщать о здоровье железа в терабайтных масштабах? Температуру каждого чипа на плате в каждую миллисекунду, причем текстовыми строками в xml (<processor_temperature>sixty-seven degrees centigrade</processor_temperature>)?

malishich
29.06.2026 02:34В C++20 у std::string_view имеется конструктор для first, last

domix32
29.06.2026 02:34Только вызывать его надо через круглые скобки. Примерно похожая история с инициализацией вектора с конкретным размером:
std::vector a{ 5, 42 }; // содержит [5,42]; std::vector b( 5, 42 ); // содержит [42,42,42,42,42] std::vector c{ a.begin(), a.end() }; // угадай что? // правильно! этом массив итераторов, а не копия aПромахнуться мимо правильного конструктора очень просто у многих подобных контейнеров в С++, а именованные конструкторы в плюсах не в почёте, поэтому и ловят рандомные логические баги из-за этого.
EDIT: был неправ, работает и с фигурными. Но проблема с неявными конструкторами присутствует. И похожу у автора проблема со стандартной библиотекой под их embed

rukhi7 Автор
29.06.2026 02:34вот так вот:
auto [first, last] = std::ranges::mismatch(a, b); size_t ptr = first; return {a.begin(), ptr};не компилируется, ошибка:
src/health/utils.hpp:хх:хх: error: invalid conversion from ‘const char*’ to ‘size_t’ {aka ‘unsigned int’} [-fpermissive] 98 | size_t ptr = first; | ^~~~~ | | | const char*а так компилируется
auto [first, last] = std::ranges::mismatch(a, b); // size_t ptr = first; const char* ptr = first; return {a.begin(), ptr};first это как бы не совсем итератор тут, на сколько я понимаю. Я вроде упомянул про то что я, например, и не пытаюсь все эти нюансы держать в голове, по моему это не возможно, когда на что-то такое надеешься надо себя обязательно проверять.

domix32
29.06.2026 02:34Какой компилятор, какие флаги? Вон ниже скинули пример на godbolt где всё прекрасно компилируется. Сам тоже пробовал и всё собралось:
мой рабочий комиплятор Clang 19 собирает это без проблем
GCC 13.1+ тоже работают как задумано. Не собралось только под gcc arm unknown eabi.
msvc 19.35 тоже собирается
Оно даже icx собирается.
Пробовал в основном x86 таргеты, но потыкал парочку arm, arm64, loongarch. Они как минимум собираются. Пробовал под AVR, но там надо ещё флагов накинуть, чтобы std подцепить.

rukhi7 Автор
29.06.2026 02:34так в том и проблема что все прекрасно компилируется, а работает не адекватно. Еще проблема в том что закопаны эти нюансы под слоями темплейтов, которые, из-за своего объема, вызывают подозрение в первую очередь и которые не очень понятно как проверять без просто перевода в рантайм.
Я особо во флаги не вникал, могу просто скопировать то что вижу:
arm-openbmc-linux-gnueabi-g++ -march=armv7-a -mfpu=vfpv4-d16 -mfloat-abi=hard -fstack-protector-strong -O2 -Os -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 <... ... ...> -flto=auto -fdiagnostics-color=always -D_GLIBCXX_ASSERTIONS=1 -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -Wpedantic -Werror -std=c++23 -O2 -g -Os <... ... ...
Это же фактически опен-соурс проект он существует годы, если не десятилетия, вряд ли можно найти специалиста, который четко на вскидку понимает как формируются эти строки компиляции. Это отдельное знание, которое вот так исторически сложилось и в котором наверно можно найти отследить какую-то свою логику если этим целенаправлено заниматься, но у нас такой задачи пока нет.
AVR это БЕЗ операционной системы насколько я знаю. Без ОС это на порядок проще обычно. Хотя ... у меня такое ощущение иногда, что ИИ (и его владельцы-операторы) усердно работает в направлении чтобы результат мог понимать только ИИ, а людей подбирают только тех кто ему в этом активно помогает, не сопротивляется.

domix32
29.06.2026 02:34так в том и проблема что все прекрасно компилируется, а работает не адекватно.
код по ссылке показывает, что работает как и ожидается. Как там обрабатывается корневой путь это отдельный вопрос, который не имеет отношения к шаблонам. Можно ещё кривых тест кейсов придумать типа "/xyz/asdqwe" vs "/xyz.qiuwrouqiw" и получить в финале "/xyz" в качестве префикса.
Это же фактически опен-соурс проект он существует годы, если не десятилетия, вряд ли можно найти специалиста, который четко на вскидку понимает как формируются эти строки компиляции.
Не очень понял к чему рант про опенсорсность. Для выплевывания флага обычно достаточно прокинуть флагов вербозности при сборке, чтобы он показывал как он компилирует файл. Но вопрос про флаги был в первую очередь из-за того что не все компиляторы используют одинаковые флаги и то как и под что вы собираете может заметно отличаться: msvc пишет флаги как
/flag:value, gcc и clang между собой частично совместимы по флагам, но скажем на каких-нибудь модулях флаги разойдутся, интеловские компиляторы емнип использовали тоже какой-то свой набор флагов не слишком совместимый с gcc и т.п. Т.к. вы явно не сказали что за проект и под какую платформу - вариантов сборки может быть уйма.first это как бы не совсем итератор тут, на сколько я понимаю
тип на который он ссылается выглядит на самом деле как
basic_string_view<char>::iterator, который в gcc объявлен вот так и фактически превращается вconst char*, так что было бы странно если бы оно сконвертировалось вsize_t. В нетривиальные типы итераторы превращаются в каких-нибудьstd::map. Да и как уже выяснили -string_viewкорректно конструируется из двух итераторов и проблема определённо связана не с классом или шаблонами, а с конкретным компилятором, который почему-то делает грязь. А может и вовсе с инстанцированием конкретных шаблонов. Скопипастив ваши шаблоны и добив кучку заглушек не столкнулся ни с какими проблемами описанными в статье. ЧЯДНТ кроме того что использую дефолтный gcc?AVR это БЕЗ операционной системы насколько я знаю
Это одна из поддерживаемых SOC архитектур. В теории ничто не мешает собрать под него ядро линукса. Сути особо не меняет, т.к. сисколы только при обращении к файлам появятся, а до них ещё дожить надо. Его взял просто как пример некоторого embed который с некоторым шансом мог оказаться в оборудовании.

rukhi7 Автор
29.06.2026 02:34Как там обрабатывается корневой путь это отдельный вопрос, который не имеет отношения к шаблонам.
С этим я совершенно согласен! Но я думаю что эту проблему сразу заметили бы и не пропустили в продуктовую версию если бы поверх этих двух строчек не нагородили слои шаблонов. Шаблоны и метапрограммирование - это другой язык, во многом ортогональный обычному С++, этот другой язык очень отвлекает внимание от практических насущных проблем реализации конкретной задачи предметной области.

domix32
29.06.2026 02:34как говорится - гусь

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

eao197
29.06.2026 02:34Но я думаю что эту проблему сразу заметили бы и не пропустили в продуктовую версию если бы
юнит-тесты на этот код были бы написаны. С разными вариантами входных значений.
Но, можно предположить, ничего подобного не было.

neon1ks
29.06.2026 02:34Согласен, легко запутаться с этими скобками у конструкторов:
std::vector<int> c1{ a.begin(), a.end() }; // Копия вектора std::vector c2{ a.begin(), a.end() }; // Вектор из двух итераторов

sergio_nsk
29.06.2026 02:34Расходимся! Это очередной высер, чтобы поднасрать С++.
std::ranges::mismatch(a, b)намекает на C++20 или новее, а в нём есть конструктор (5)template< class It, class End > constexpr basic_string_view( It first, End last );И простой тест показывает правильный результат "/"
#include <iostream> #include <ranges> #include <string_view> constexpr std::string_view commonPrefixOfTwo(std::string_view a, std::string_view b) { auto [first, last] = std::ranges::mismatch(a, b); return {a.begin(), first}; // Запомните эту строчку! } int main() { constexpr auto common = commonPrefixOfTwo("/xyz/openbmc_project/sensors/", "/com/swtSyst"); std::cout << common << "\n"; }Да и в С++17 без ranges это бы не скомпилировалось. Потому что никаких неявных преобразований
const char*вsize_tне происходит!error: no matching constructor for initialization of 'std::string_view' (aka 'basic_string_view<char>') note: candidate constructor not viable: no known conversion from 'const char *const' to 'size_type' (aka 'unsigned long') for 2nd argument; dereference the argument with *
neon1ks
29.06.2026 02:34Добавлю от себя продолжение: Потому что синтаксис с фигурными скобками запрещает неявное приведение типов.

sergio_nsk
29.06.2026 02:34Дополню. Вообще, если статья была бы правдой, то неверный результат возник бы раньше, а не после добавления "/com/swtSyst". Возник бы вопрос, почему не приходят события "fan/tach".

ZamirHa
29.06.2026 02:34Любой язык проходит путь от начальной реализации до варианта, который идеально соответствует первоначально заложенной в него идеологии.
На этом бы остановиться, но увы - в него начинают вкрячивать всякие полезные фичи, дополнительные тулзы и пр. дребедень "для улучшения". "ускорения", "соответствия современным парадигмам" и т.п. И язык превращается в дикое уе***ще, эдакого монстра.
Вокруг которого обязательно кучкуются жрецы этого культа, повторяющие как заведенные: вы просто не умеете этим правильно пользоваться.
Жутко на это со стороны смотреть, если честно. А я ведь когда-то на С++ прогал и мне он нравился...

AlexSky
29.06.2026 02:34В итоге
std::string_viewподумал, что от него требуют: «Возьми строку "a" и отмерь от её начала кусок длиной в 140 триллионов символов».Но
std::string_view— штука не глупая. Внутри она хранит исходную длину строкиa(длину пути сенсоров).Вот тут совсем не понял. std::string_view просто сохранит указатель и длину строки.
std::string_view s = {"abc", 666666666};printf("%lu\n", s.size());output: 666666666

rukhi7 Автор
29.06.2026 02:34вот так вот надо проверять:
cons char * ptr = "dummy string"; std::string_view s = {"abc", ptr}; printf("%lu\n", s.size());
AlexSky
29.06.2026 02:34То же самое:
$ g++ -std=c++20 a.cpp && ./a.out
18446744073709551603
neon1ks
29.06.2026 02:34Результат, это расстояние между двумя указателями, которые указывают на разные строки в разных частях памяти. Нужно, чтобы указатели принадлежали одной строке, тогда будет правильно.

neon1ks
29.06.2026 02:34#include <cstdio> #include <string> #include <string_view> int main() { const char * ptr = "dummy string"; std::string_view s1 = {ptr, ptr + 5}; std::string_view s2 = {ptr, ptr + 20}; printf("'%s' have size %zu\n", std::string(s1).c_str(), s1.size()); printf("'%s' have size %zu\n", std::string(s2).c_str(), s2.size()); return 0; }Вывод:
‘dummy’ have size 5 ‘dummy string’ have size 20Если второй указатель выходит за границы строки, то размер будет неверным.

neon1ks
29.06.2026 02:34Ошибка на ошибке.
Размерsize_tзависит от архитектуры системы, где то это 4 байта, а где то это 8 байт. Дляsize_tнужно использовать%zu:cons char * ptr = "dummy string"; std::string_view s = {"abc", ptr}; printf("%zu\n", s.size()); // %zuА во вторых, вы вообще думаете, что здесь делаете? Вы передаете указатели на разные строки. И
string_viewнаходит расстояние между этими строками в памяти, а они находятся в разных частях памяти. Указатели, передаваемые вstring_view, должны принадлежали одной строке.
netricks
Когда-то и я постигал искусство шаблона. А потом мне прострелило колено.
Как хорошо сказал один замечательный человек (возможно, это даже был я) - если у вас в коде много шаблонов, значит вы упустили возможность решить задачу просто.
Ну и constexpr тоже. C++ куда-то не туда заигрывает в своих стремлениях строить языки поверх языка. Если коду действительно нужен constexpr, то, вполне вероятно, коду на самом деле нужна кодогенерация (одна небольшая утилита рядом на каких-нибудь скриптах (python), запускаемая кастомной целью перед сборкой, решает любые проблемы по нетривиальным compile time вычислениям)
yurrig
При правильном использовании constexpr оч даже полезен. Например, можно сделать enum из функции от строк, и потом switch по этой ф-ции от входной строки (т.е. как-бы switch по строкам), что сильно ускоряет чтение некоторых форматов по сравнению с поиском по хэш-таблицам. Если бездумно валить все в кучу, на любом языке получится багатый говнокод. Дело тут не в языке, а в ясности мысли, или отсутствии таковой. Кодогенерация - вешь отличная, сам вовсю использую, но часто это из пушки по воробьям.