Обновить

Если ссылки схлопываются, значит это кому‑то нужно

Уровень сложностиСложный
Время на прочтение10 мин
Охват и читатели14K
Всего голосов 14: ↑14 и ↓0+21
Комментарии10

Комментарии 10

Тот же баг получится и без шаблонов

На этом рассуждения про “проблемы” с шаблонами и жутью про защищенные страницы можно было бы и завершить. Потому что шаблоны здесь не при чем от слова совсем.

Технически верно, я об этом написал, шаблон этот баг не создаёт, он в принципе не про шаблоны и дыра целиком на совести const& + mutable. Но статья не о том, что шаблоны опасны сами по себе, но что вывод типов через шаблон меняет, как читается const T& и разработчик перестаёт держать в голове вопрос “а что мне реально прилетело”.

Обычная функция с явной сигнатурой const GruMemory& тот же баг пропустит, но сигнатура написана руками и типы видны сразу и шанс заметить проблему на кодревью выше. Шаблон здесь выступает усилителем непрозрачности. Если там сделать T& то мы можем внутри проверять конст объект пришел или не конст, а с концептами и вовсе запретить такие подлые функции

https://godbolt.org/z/qY7xjhfxK

template <typename T> requires (!std::is_const_v<T>)
auto& get_cache(T& obj) { ... }

Возможно я понимаю ваши намерения. Но не согласен с вашей точкой зрения.

До этого примера материал в статье не вызывал вопросов. Но когда этот пример появился, возникло ощущение, что вы только зря “тень на плетень” наводите. Что он не иллюстрирует тот момент, который вы хотели показать, а только усложняет восприятие и запутывает читателя.

Собственно, как и следующий пример с auto n = v; в функции normalize. Те же самые грабли есть и в обычных, не шаблонных, функциях.

Но статья не о том, что шаблоны опасны сами по себе, но что вывод типов через шаблон меняет, как читается const T& и разработчик перестаёт держать в голове вопрос “а что мне реально прилетело”.

мне кажется или как раз с const& шаблон ничего не меняет? Тут мне всегда прилетает константная ссылка. А что там в объекте за mutable и const_cast - это его проблемы. Он должен сам свои инварианты соблюдать.

А вот с T& вы правы. Не совсем очевидно что тут может прилететь константная ссылка. Но, с другой стороны, - это имеет какое-то значение? Кроме как для чтения кода. Ведь если кто-то передаст const int&, то код просто не скомпилируется (если и правда обращается к неконстантным методам).

Не совсем ни при чем.

Когда в шаблоне [вложенном] указано <T>, а прилететь туда может и int и const int&& и внутри надо ожидать что угодно. В том числе, что внешний шаблон тоже может добавлять свои квалификаторы.

Т.е.с ними проблема просто усугубляется неявностью и сложностью.

Не совсем ни при чем.

Есть простой критерий: если проблема воспроизводится в сценарии без шаблона, значит шаблон не при чем.

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

В результате функция может внезапно начать работать с объектами, которые «действительно» нельзя изменять, даже если автор функции этого не ожидал, что может полностью изменить смысл функции.

Ну что это за гптшные кавычки ни к селу ни к городу? Люди так не пишут!

Что приводит нас к очень странному поведению и коварным ошибкам при проектировании интерфейсов, если вы по привычке «пишете const T& где надо и где не надо». Многие делают это автоматически, потому что словили разные баги в детстве и считают, что таким образом избегают копирования и работают с обычным, изменяемым объектом, но это не так.

Просто ШТО... Я в ауте немного... Как можно написать const T& и считать, что работаешь с изменяемым объектом? Типа, такое заблуждение тут же развеивается CE при попытке сделать неконстантную операцию. Или IDE красным подсветит... Ничего не понимаю!

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

Хм, ок и ваше мнение тоже важно, на первую версию среди друзей знакомых наборот были нарекания что примеры не разобраны.

Как можно написать const T& и не знать про это

void foo(const T& x) { x.field = 5; }

Вот тут CE словит, ибо так нельзя. Потом приходит техдир и говорит "не хочу говорит, чтобы все создаваемые текстуры сразу грузили свои данные, подавай мне lazy загрузку, и чтобы можно было с двух потоков это дело подгружать тоже lazy".

rgba* get_rgba(const T& tx) { if (!tx.rgba) tx.load_rgba(); return tx.rgba; }

Текстура сюда const приходит? конст... а данные в ней не конст, она их загрузит на первом обращении. Это легально и это работает уже десятки лет, get_rgba принимает const T& пишет он в mutable-поле, а вызывается на объекте, который сам по себе изменяемый, хотя и помечен конст и с точки зрения системы типов не нарушено ничего.

А теперь в проекте появляются текстуры, которые константны по-настоящему, потому что видеокарта пометила их страницу как read-only. И тут нам очень бы хотелось, чтобы get_rgba их отличал, и для такой текстуры вызывать load_rgba() нельзя, там запись полетит в защищённую страницу. Но только отличить не выйдет, в шаблоне const T& один const уже потрачен на сам шаблон ибо мы думали про работу со ссылкой, и второй const аргумента ему просто некуда записать, он совпадает с шаблонным и был сьеден, а T вышел чистым. Из реального const T, он стал просто T и передавая его дальше по флоу, вы эту информацию потеряли.

template <typename T>
rgba* get_rgba(T& tx) {
    if constexpr (std::is_const_v<std::remove_reference_t<T>>) {
        // настоящая read-only текстура, её грузить нельзя, только читать готовое
        verify_crash(tx.has_pixels() && "read-only texture accessed before upload");
        return tx.pixels;
    } else {
        if (!tx.has_pixels())
            tx.load_rgba();
        return tx.pixels;
    }
}

Вот и получается, что если вы в шаблоне написал const T& то получили логику "принимаю всё, что const-совместимо, и забываю, если пришёл const const T&"

template <typename T> rgba* get_rgba(T& tx)

ИМХО, здесь universal references (как их называл Мейерс) были бы более уместны.

В данном случае это уже не проблема шаблона, ибо, например, следующая функция будет иметь ту же проблему:

rgba* get_rgba(Texture const &t)
{
  if (!t.has_pixels())
    t.load_rgba();
  return t.pixels;
}

На самом деле проблема кроется в использовании mutable, который имитирует const_cast для бизнес данных объекта, что делать не должен, ибо не предназначен для такой цели.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации