LINUX.ORG.RU

C++ UB: vector<bool>

 ,


0

2

Забавный С++

#include <vector>

template <typename T>
constexpr auto f(const T &value) {
    std::vector<T> vec(1, value);
    return vec[0];
}

static_assert(f(true));

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

А вы видите в этом коде UB?

★★★★★
Ответ на: комментарий от yoghurt

UB начинается тогда, когда код всё же скомпилировался

Компиляторы стараются ловить UB в constexpr контексте и останавливают компиляцию если находят UB.

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

fsb4000 ★★★★★
() автор топика

А вы видите в этом коде UB?

Могу предположить, что дело в том, что std::vector::operator[] возвращает ссылку, соответственно, f() так же возвращает ссылку. Но это уже ссылка на уничтоженный временный объект.

Правда, меня смущает то, что функция auto f() сохраняет ссылку для результата. Почему-то казалось что для такого требуется decltype(auto) f().

eao197 ★★★★★
()
Ответ на: комментарий от eao197

@eao197 да, верно, std::vector::operator[] возвращает vector<bool>::reference который является прокси-объектом, который может приводиться к bool, и там хранится ссылка на локальный контейнер, который уже будет уничтожен по выходу из функции.

Это именно только vector<bool> так работает, из-за того что он проектировался для экономии памяти.

Ну и пофиксить тоже тривиально, нужно заменить auto на T.

#include <vector>

template <typename T>
constexpr T f(const T &value) {
    std::vector<T> vec(1, value);
    return vec[0];
}

static_assert(f(true));

https://en.cppreference.com/w/cpp/container/vector_bool

fsb4000 ★★★★★
() автор топика
Последнее исправление: fsb4000 (всего исправлений: 1)

vector<bool>

Один раз словил по щщам, когда пытался одновременно писать по разным индексам из разных потоков. Эта специализация такое не позволяет. Пришлось заменить на вектор интов.

ox55ff ★★★★★
()

Начнем с того, что constexpr и объект, хранящий что-то в динамической памяти, это несовместимые вещи.

Вот что случается с людьми, у которых мозг поврежден C++, и они не понимают про compile-time vs run-time, как лисперы например.

lovesan ★★★
()
Ответ на: комментарий от fsb4000

Это именно только vector так работает, из-за того что он проектировался для экономии памяти.

Оно биты хранит чтоли?

X512 ★★★★★
()
Ответ на: комментарий от xor2003

Для других типов не ссылка будет?

Будет ссылка на T&, который при возвращаемом типе auto преобразуется в T, и всё норм будет.

А тут ссылка на прокси элемент, который хранит индекс и ссылку на вектор. И у которого есть operator bool. И возвращается прокси элемент, а не bool.

Вот часть этого класса, как может выглядеть:

class _Vb_reference  {
   public:
    constexpr _Vb_reference& operator=(const _Vb_reference& _Right) noexcept {
        return *this = static_cast<bool>(_Right);
    }

    constexpr _Vb_reference& operator=(bool _Val) noexcept {
        if (_Val) {
            *const_cast<_Vbase*>(_Getptr()) |= _Mask();
        } else {
            *const_cast<_Vbase*>(_Getptr()) &= ~_Mask();
        }

        return *this;
    }

    constexpr operator bool() const noexcept {
        return (*_Getptr() & _Mask()) != 0;
    }
};
fsb4000 ★★★★★
() автор топика

Сообразить не могу, а какой резон возвращать прокси объект, почему не вернуть БУЛ сразу? Про хранение нескольких булов в одном байте - понятно.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

почему не вернуть БУЛ сразу?

  1. Мы храним не bool, а int/int64, и там храним по 32/64 була. Для экономии памяти.

  2. Мы должны позволять менять значение также, а адресовать бит не можем. То есть чтобы такое работало:

void set_bit(std::vector<bool>& v) {
    v[0] = false;
}
fsb4000 ★★★★★
() автор топика
Ответ на: комментарий от kvpfs

а какой резон возвращать прокси объект

vec[0] должен возвращать ссылку. Но ссылки на бит быть не может. Поэтому возвращается прокси объект, который реализует требуемое поведение.

ox55ff ★★★★★
()
Ответ на: комментарий от utf8nowhere

UB может быть когда угодно, хоть на этапе препроцессинга.

+1, я помню один из примеров такого UB:

It is undefined behavior if defined is generated as a result of macro expansion.

https://eel.is/c++draft/cpp.predefined#4

fsb4000 ★★★★★
() автор топика

«Avoid using vector<bool>» (c) Любые колокола звонили?

slackwarrior ★★★★★
()
Ответ на: комментарий от fsb4000

Если убрать static_assert, то код скомпилируется

Если убрать статик ассерт, шаблон не инстанциируется, соответственно и не компилируется

yoghurt ★★★★★
()
Ответ на: комментарий от fsb4000

Не знаю что у вас там, у меня ошибка компиляции возникла вовсе не из-за уб, а из-за того, что оператор bool() типа ссылки на элемент вектора bool не constexpr.

Элементарная нестыковка, в общем

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

https://gcc.godbolt.org/z/oxxPnezMo

https://gcc.godbolt.org/z/9fzrhzKsz

https://gcc.godbolt.org/z/o94d1Yq8P

error: use of allocated storage after deallocation in a constant expression

Всё constexpr, и всё компилируется без static_assert, в тегах С++20

fsb4000 ★★★★★
() автор топика
Последнее исправление: fsb4000 (всего исправлений: 3)
Ответ на: комментарий от yoghurt

Если что

dm@thinkpad:/tmp$ g++ --version
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

dm@thinkpad:/tmp$ cat file.cc 
#include <vector>

template <typename T>
constexpr auto f(const T &value) {
    constexpr std::vector<T> vec(1, value);
    return vec[0];
}

static_assert(f(true));
dm@thinkpad:/tmp$ g++ -std=c++2a file.cc 
file.cc: In instantiation of ‘constexpr auto f(const T&) [with T = bool]’:
file.cc:9:21:   required from here
file.cc:5:30: error: the type ‘const std::vector<bool, std::allocator<bool> >’ of ‘constexpr’ variable ‘vec’ is not literal
    5 |     constexpr std::vector<T> vec(1, value);
      |                              ^~~
In file included from /usr/include/c++/9/vector:68,
                 from file.cc:1:
/usr/include/c++/9/bits/stl_bvector.h:592:11: note: ‘std::vector<bool, std::allocator<bool> >’ is not literal because:
  592 |     class vector<bool, _Alloc> : protected _Bvector_base<_Alloc>
      |           ^~~~~~~~~~~~~~~~~~~~
/usr/include/c++/9/bits/stl_bvector.h:592:11: note:   ‘std::vector<bool, std::allocator<bool> >’ has a non-trivial destructor
file.cc:9:16: error: non-constant condition for static assertion
    9 | static_assert(f(true));
      |               ~^~~~~~
file.cc:9:16: error: ‘constexpr auto f(const T&) [with T = bool]’ called in a constant expression
file.cc:4:16: note: ‘constexpr auto f(const T&) [with T = bool]’ is not usable as a ‘constexpr’ function because:
    4 | constexpr auto f(const T &value) {
      |                ^
file.cc:5:30: error: call to non-‘constexpr’ function ‘std::vector<bool, _Alloc>::vector(std::vector<bool, _Alloc>::size_type, const bool&, const allocator_type&) [with _Alloc = std::allocator<bool>; std::vector<bool, _Alloc>::size_type = long unsigned int; std::vector<bool, _Alloc>::allocator_type = std::allocator<bool>]’
    5 |     constexpr std::vector<T> vec(1, value);
      |                              ^~~
yoghurt ★★★★★
()
Ответ на: комментарий от intelfx

Ага. Причем разница принципиальная :)

#include <iostream>
#include <type_traits>

template<typename T>
auto just_auto(T & obj) { return obj.m(); }

template<typename T>
decltype(auto) decltype_auto(T & obj) { return obj.m(); }

struct holder {
  int v_;

  int & m() { return v_; } // Возвращается именно ссылка на int.
};

int main() {
    holder h{42};

    static_assert(std::is_same<decltype(just_auto(h)), int>::value, "expect `int`");
    static_assert(std::is_same<decltype(decltype_auto(h)), int&>::value, "expect `int&`");
}

https://wandbox.org/permlink/qakKOjaiHKlX1r1p

eao197 ★★★★★
()

А нафиг всё это городить и не написать явно, вот будто в реальности надо под любой тип генерить функцию. Куда не глянь одни везде template для штук которые используются один раз, с одним/двумя типами в одном двух местах. Човобы не написать всё явно? У вас секта какайто? Йя нимагу. Раньше гуляешь по форумам где хоть слово про с++ так там везде «Тебе нужно создать класс!» и описывает пустую заглушку и так далее я создал класс, оцените мой класс, как сделать такой класс. Причём выливалось тоннны кода пустых заготовок в виде этих классов вместо тел функций просто ... как будто мерялись тем кто как красивее отформатирует свой Class.

Вот ща такая же фигня пошла с template городят внутри такое что барсикам не снилось. Притом что внутри фигня на две строчки, зачеееем? ПАЧАМУ ВЫ ТАК ДЕЛАИТИИИИ? Аааааааа, chto с вами такоее случилось человекиии :D

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

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

Вот это обращение:

https://developercommunity.visualstudio.com/t/Element-accesses-on-std::vector-lt;-boo/10088801?space=62&q=vector%3Cbool%3E

P.S. Там забыли обновить статус, нужно «Closed, Not A Bug»

fsb4000 ★★★★★
() автор топика
Ответ на: комментарий от PhysShell

Там обычно предложения уровня «нафига это назвали vector<bool> если оно не хранит bool?» Т.е. требуют привести название в соответствие с реализацией

slackwarrior ★★★★★
()
Ответ на: комментарий от bugfixer

почему в заголовке указано Microsoft Word - N1211.doc ))))

zerhud
()
Ответ на: комментарий от PhysShell

Я не знаю что именно там будут обсуждать, но 25 июля пленарное заседание комитета на котором примут последние новые возможности C++23 и после этого он станет «Feature Complete».

fsb4000 ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Говоря без каких либо претензий, но даже судя по ОП люди натыкаются на эти грабли неочевидности из с++-98 уже в «modern c++».

И было интересно узнать, рассматривают ли данный вопрос в комитете на каком-нибудь уровне, кроме как то, о чём написал выше @slackwarrior.

PhysShell ★★
()
Ответ на: комментарий от utf8nowhere

Это да, вопросов никаких, обходится довольно безболезненно.

PhysShell ★★
()

Есть несколько правил, которые стабильно помогают мне избегать падений на эти и похожие грабли:

  1. Не писать на с++ без реальной на то необходимости
  2. Не писать обобщенный код без реальной на то необходимости
  3. Не использовать auto там, где он не упрощает чтение кода
  4. Не использовать булевый вектор.
filosofia
()
Ответ на: комментарий от seiken

Не припомню, когда мне в последний раз приходилось хранить МНОГО булевых значений вместе, это очень специфичный кейз. А так вектор байт или uint64 с маской.

filosofia
()
Ответ на: комментарий от filosofia

МНОГО булевых значений вместе, это очень специфичный кейз

  • результаты испытаний с с.в. с распределением Бернулли
  • результаты классификации (да/нет)
seiken ★★★★★
()
Ответ на: комментарий от SpaceRanger

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

thesis ★★★★★
()
Ответ на: комментарий от filosofia

я тоже в тех областях не работаю :) просто привел примеры, которые сразу на ум пришли

seiken ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.