Комментарии 27
Когда-то и я постигал искусство шаблона. А потом мне прострелило колено.
Как хорошо сказал один замечательный человек (возможно, это даже был я) - если у вас в коде много шаблонов, значит вы упустили возможность решить задачу просто.
Ну и constexpr тоже. C++ куда-то не туда заигрывает в своих стремлениях строить языки поверх языка. Если коду действительно нужен constexpr, то, вполне вероятно, коду на самом деле нужна кодогенерация (одна небольшая утилита рядом на каких-нибудь скриптах (python), запускаемая кастомной целью перед сборкой, решает любые проблемы по нетривиальным compile time вычислениям)
При правильном использовании constexpr оч даже полезен. Например, можно сделать enum из функции от строк, и потом switch по этой ф-ции от входной строки (т.е. как-бы switch по строкам), что сильно ускоряет чтение некоторых форматов по сравнению с поиском по хэш-таблицам. Если бездумно валить все в кучу, на любом языке получится багатый говнокод. Дело тут не в языке, а в ясности мысли, или отсутствии таковой. Кодогенерация - вешь отличная, сам вовсю использую, но часто это из пушки по воробьям.
Чтобы не захлебнуться в терабайтах системных сообщений
А что вообще можно сообщать о здоровье железа в терабайтных масштабах? Температуру каждого чипа на плате в каждую миллисекунду, причем текстовыми строками в xml (<processor_temperature>sixty-seven degrees centigrade</processor_temperature>)?
В C++20 у std::string_view имеется конструктор для first, last
Только вызывать его надо через круглые скобки. Примерно похожая история с инициализацией вектора с конкретным размером:
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
вот так вот:
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 это как бы не совсем итератор тут, на сколько я понимаю. Я вроде упомянул про то что я, например, и не пытаюсь все эти нюансы держать в голове, по моему это не возможно, когда на что-то такое надеешься надо себя обязательно проверять.
Какой компилятор, какие флаги? Вон ниже скинули пример на godbolt где всё прекрасно компилируется. Сам тоже пробовал и всё собралось:
мой рабочий комиплятор Clang 19 собирает это без проблем
GCC 13.1+ тоже работают как задумано. Не собралось только под gcc arm unknown eabi.
msvc 19.35 тоже собирается
Оно даже icx собирается.
Пробовал в основном x86 таргеты, но потыкал парочку arm, arm64, loongarch. Они как минимум собираются. Пробовал под AVR, но там надо ещё флагов накинуть, чтобы std подцепить.
так в том и проблема что все прекрасно компилируется, а работает не адекватно. Еще проблема в том что закопаны эти нюансы под слоями темплейтов, которые, из-за своего объема, вызывают подозрение в первую очередь и которые не очень понятно как проверять без просто перевода в рантайм.
Я особо во флаги не вникал, могу просто скопировать то что вижу:
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 это БЕЗ операционной системы насколько я знаю. Без ОС это на порядок проще обычно. Хотя ... у меня такое ощущение иногда, что ИИ (и его владельцы-операторы) усердно работает в направлении чтобы результат мог понимать только ИИ, а людей подбирают только тех кто ему в этом активно помогает, не сопротивляется.
так в том и проблема что все прекрасно компилируется, а работает не адекватно.
код по ссылке показывает, что работает как и ожидается. Как там обрабатывается корневой путь это отдельный вопрос, который не имеет отношения к шаблонам. Можно ещё кривых тест кейсов придумать типа "/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 который с некоторым шансом мог оказаться в оборудовании.
Как там обрабатывается корневой путь это отдельный вопрос, который не имеет отношения к шаблонам.
С этим я совершенно согласен! Но я думаю что эту проблему сразу заметили бы и не пропустили в продуктовую версию если бы поверх этих двух строчек не нагородили слои шаблонов. Шаблоны и метапрограммирование - это другой язык, во многом ортогональный обычному С++, этот другой язык очень отвлекает внимание от практических насущных проблем реализации конкретной задачи предметной области.
как говорится - гусь

Но вообще понимаю шаблонную боль. В своё время требовалось писать расширения для игрового движка. А для того чтобы это сделать требовалось обойти несколько уровней индирекций шаблонов с доступами к полям и методам класса, навернуть собственных шаблонов сверху и надеяться, что вся эта ватага скомпилируется без ошибок.
Но я думаю что эту проблему сразу заметили бы и не пропустили в продуктовую версию если бы
юнит-тесты на этот код были бы написаны. С разными вариантами входных значений.
Но, можно предположить, ничего подобного не было.
Согласен, легко запутаться с этими скобками у конструкторов:
std::vector<int> c1{ a.begin(), a.end() }; // Копия вектора
std::vector c2{ a.begin(), a.end() }; // Вектор из двух итераторовРасходимся! Это очередной высер, чтобы поднасрать С++.
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 *Любой язык проходит путь от начальной реализации до варианта, который идеально соответствует первоначально заложенной в него идеологии.
На этом бы остановиться, но увы - в него начинают вкрячивать всякие полезные фичи, дополнительные тулзы и пр. дребедень "для улучшения". "ускорения", "соответствия современным парадигмам" и т.п. И язык превращается в дикое уе***ще, эдакого монстра.
Вокруг которого обязательно кучкуются жрецы этого культа, повторяющие как заведенные: вы просто не умеете этим правильно пользоваться.
Жутко на это со стороны смотреть, если честно. А я ведь когда-то на С++ прогал и мне он нравился...
В итоге
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
вот так вот надо проверять:
cons char * ptr = "dummy string";
std::string_view s = {"abc", ptr};
printf("%lu\n", s.size()); То же самое:
$ g++ -std=c++20 a.cpp && ./a.out
18446744073709551603
Результат, это расстояние между двумя указателями, которые указывают на разные строки в разных частях памяти. Нужно, чтобы указатели принадлежали одной строке, тогда будет правильно.
#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 Если второй указатель выходит за границы строки, то размер будет неверным.
Вы правы! Это не настоящая причина ошибки, но она идеально подходила для статьи - достаточно простая для статьи и верно указывала локализацию ошибки.
Настоящая причина ошибки гораздо сложнее (но это по моему только подтверждает все основные мысли в статье):
Настоящая причина ошибки
По выводу сборки Yocto/OpenBMC можно определить версию GCC. Путь к стандартной библиотеке у меня в логе:
/usr/include/c++/15.1.0/format
Мы собираем проект с помощью GCC 15.1.0 (экспериментальная или самая свежая версия компилятора на момент сборки ветки OpenBMC).
В компиляторах GCC, начиная с версии 14.1 и выше (включая нашу 15.1), был изменён внутренний тип итератора для std::string_view ради оптимизации std::ranges. Теперь std::string_view::iterator — это не просто голый указатель const char*, а специальный класс-обёртка (снапшот структуры __normal_iterator).
Вот как компилятор обманул код в GCC 15
Когда мы пишем return {a.begin(), first};:
a.begin()имеет типstd::string_view::iterator(который в GCC 15 является типом-обёрткой).Переменная
firstпорождается изstd::ranges::mismatch(a, b). Так какa— этоstd::string_view,firstимеет точно такой же типstd::string_view::iterator.Компилятор видит два аргумента одинакового типа внутри фигурных скобок
{ ... }.-------------------------------------------------------------------------------------
Он отбрасывает правильный конструктор
(It first, End last), потому что тот помечен какexplicit(а фигурные скобки запрещают явные конструкторы).------------------------------------------------------------------------------------
И вот тут срабатывает ловушка GCC: В коде стандартной библиотеки libstdc++ для GCC 14/15 у класса
std::string_view(или его итераторов) из-за поддержки концептов (Concepts) и диапазонов (Ranges) есть скрытые внутренние не-explicit конструкторы или операторы приведения типов (например, для совместимости сstd::initializer_listили внутренними структурами итераторов).Вместо того чтобы выдать ошибку, компилятор находит этот не-явный внутренний путь перегрузки. Но этот путь не предназначен для создания подстроки! В результате итератор
firstигнорируется или сбрасывается вa.end(), и функция молча возвращает исходную строкуaцеликом, порождая указанную ошибку.
Попробуйте все это запомнить и не забыть в самый ответственный момент, ну или найти способ проверить в самый ответственный момент.
Не воспроизводится. Не можешь угомониться? Ты бы хоть проверил текст ИИ, которого ты принуждаешь согласиться с тобой.a.begin() - всё ещё 'std::basic_string_view::const_iterator' {aka 'const char*'} .
Так же first - это const char* .
Он отбрасывает правильный конструктор
(It first, End last), потому что тот помечен какexplicit
Хватит врать! Нет там explicit. Ты хоть знаешь для чего он? Он не имеет смысла для конструктора с двумя параметрами.
а фигурные скобки запрещают явные конструкторы
Какой-то бред.
на моем кросс-компиляторе воспроизводится:
<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/]не надо так нервничать. Зачем вы читаете то что вас выводит из себя?
Наверно если бы Нео в деталях рассказали куда он попадет, перед тем как предложить таблетку, он бы тоже так нервничал и ругался. Но конкретно вас ведь никто не призывает покинуть ваш уютный мир, где все нагромождения привычны и внушают только доверие. Не читайте бред, читайте то что вас успокаивает.
Ошибка на ошибке.
Размер 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, должны принадлежали одной строке.

Отрежьте мне миллиард символов: как C++20, string_view и шаблонный ад могут скрывать баг годами