constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 17.11.19 20:17
Оценка: 9 (1)
Приветствую всех С++ гуру. На днях столкнулся с очередной особенностью С++17, а именно: невозможность преобразовать указатели разных типов друг к другу в compile-time.
Мне в моей библиотеке необходим специальный класс Buffer, для работы с буферами данных разного типа, в котором хотелось бы иметь возможность работать с указателями во время компиляции, в том числе. Проблема:
// Вариант первый:
class Buffer
{
public:
    
    constexpr Buffer(void* data, size_t size) : m_data(data), m_size(size) {}

    constexpr Bufer& operator++(); // ??? 

//...
private:

    void* m_data;
    size_t m_size;
};

принимает любой указатель без необходимости явного преобразования, но после, непонятно как реализовать операцию инкремента, т.к. компилятор запрещает преобразования между указателями void* и, например, uint8_t* — для того чтобы можно было как-то сдвинуть указатель буфера.
// Вариант второй:
class Buffer
{
public:
    
    constexpr Buffer(uint8_t* data, size_t size) : m_data(data), m_size(size) {}

    constexpr Bufer& operator++() { m_data++; return *this; } // Нет проблем!!!

//...
private:

    uint8_t* m_data;
    size_t m_size;
};

работает, но теперь неудобно работать с классом, так как нужно явно кастировать указатели разных типов, в том числе и void*, к указателям на uint8_t*.

Класс Buffer является оберткой для "сырых" данных. Размер m_size для любых буферов указывается в байтах. Операция инкремент должна сдвигать буфер на один байт. Просьба помочь с дизайном и заодно советом, как реализовать операцию инкремент в compile-time для такого случая, или объяснить почему это не возможно.
Re: constexpr и преобразование указателей
От: rg45 СССР  
Дата: 17.11.19 20:48
Оценка:
Здравствуйте, Videoman, Вы писали:


V>Класс Buffer является оберткой для "сырых" данных. Размер m_size для любых буферов указывается в байтах. Операция инкремент должна сдвигать буфер на один байт. Просьба помочь с дизайном и заодно советом, как реализовать операцию инкремент в compile-time для такого случая, или объяснить почему это не возможно.


Так оставь в объявлении члена uint8_t*, а в конструкторе void*. Таким образом, касты становятся деталью реализации класса и не затрагивают клиентский код. При желании преобразования можно локализовать в специальных закрытых функциях так, что это не будет доставлять дискомфорта даже при имплементации самого класса.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: constexpr и преобразование указателей
От: prog123 Европа  
Дата: 17.11.19 21:56
Оценка: 3 (1)
Здравствуйте, Videoman, Вы писали:

union?

#include <iostream>
#include <cinttypes>
struct Buffer
{
    Buffer(void* data, size_t size) : data_void(data), size(size) { }
    Buffer& operator ++() { data_uint8++; return *this; }
    union
    {
        void* data_void;
        uint8_t* data_uint8;
    };
    size_t size;
};

int main()
{
    char const* a = "abc";
    Buffer ba((void*)a, sizeof(a));
    ++ba;
    std::cout << static_cast<char*>(ba.data_void) << std::endl;
    std::cout << reinterpret_cast<char*>(ba.data_uint8) << std::endl;
    return 0;
}


output:
bc
bc
Re[2]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 09:55
Оценка:
Здравствуйте, rg45, Вы писали:

R>Так оставь в объявлении члена uint8_t*, а в конструкторе void*. Таким образом, касты становятся деталью реализации класса и не затрагивают клиентский код. При желании преобразования можно локализовать в специальных закрытых функциях так, что это не будет доставлять дискомфорта даже при имплементации самого класса.


Ну, собственно, так и было сделано до constexpr. m_data был void*, но как только к конструктору или оператору добавляется constexpr, компилятор тут же начинает ругаться что static_cast<uint8_t*>(m_data) — не разрешен во времени компиляции. В этом и вопрос, а как "двигать" указатель без каста тогда?
Re[2]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 09:57
Оценка:
Здравствуйте, prog123:

Прошу еще раз внимательно прочитать вопрос. Все происходит в constexpr конструкторах и методах и такой layout данных там, тем более, запрещен.
Re[3]: constexpr и преобразование указателей
От: rg45 СССР  
Дата: 18.11.19 11:52
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Ну, собственно, так и было сделано до constexpr. m_data был void*, но как только к конструктору или оператору добавляется constexpr, компилятор тут же начинает ругаться что static_cast<uint8_t*>(m_data) — не разрешен во времени компиляции. В этом и вопрос, а как "двигать" указатель без каста тогда?


А вот так работает :

http://coliru.stacked-crooked.com/a/23431aeebb4072fd

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

class Buffer
{
public:
    
    template <typename T>
    constexpr Buffer(const T& data) : Buffer(&data, sizeof(data)) {}

    constexpr Buffer(const void* data, size_t size) : m_data(bytes(data)), m_size(size) {}

    constexpr Buffer& operator++() { m_data++; return *this; } // Нет проблем!!!

    constexpr size_t size() const { return m_size; }

//...
private:

    static constexpr uint8_t* bytes(const void* data) { return static_cast<uint8_t*>(const_cast<void*>(data)); }

    uint8_t* m_data;
    size_t m_size;
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 18.11.2019 12:16 rg45 . Предыдущая версия . Еще …
Отредактировано 18.11.2019 12:15 rg45 . Предыдущая версия .
Отредактировано 18.11.2019 12:01 rg45 . Предыдущая версия .
Отредактировано 18.11.2019 11:59 rg45 . Предыдущая версия .
Отредактировано 18.11.2019 11:58 rg45 . Предыдущая версия .
Re[4]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 12:50
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>А вот так работает :


R>http://coliru.stacked-crooked.com/a/23431aeebb4072fd


У меня не пашет под VS 2017. Выдает такое:

d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(74): error C2131: expression did not evaluate to a constant
d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(54): note: failure was caused by a conversion from void* to a pointer-to-object type

Re[5]: constexpr и преобразование указателей
От: rg45 СССР  
Дата: 18.11.19 13:39
Оценка:
Здравствуйте, Videoman, Вы писали:

R>>http://coliru.stacked-crooked.com/a/23431aeebb4072fd


V>У меня не пашет под VS 2017. Выдает такое:

V>

V>d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(74): error C2131: expression did not evaluate to a constant
V>d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(54): note: failure was caused by a conversion from void* to a pointer-to-object type


Да, и у меня то же самое. Я сейчас тяну обновления для студий: 2017 — 5.9.17 и 2019 — 6.3.9. Посмотрим, что скажут последние версии.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[6]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 13:57
Оценка: 9 (1)
Здравствуйте, rg45, Вы писали:

R>Да, и у меня то же самое. Я сейчас тяну обновления для студий: 2017 — 5.9.17 и 2019 — 6.3.9. Посмотрим, что скажут последние версии.


У меня рабочая версия VS 2017 — 15.9.17 (последняя) — на ней не работает. На 19-ой под https://gcc.godbolt.org/ — вроде как работает, но к сожалению это пока не мой вариант

Насколько я понимаю, это ограничения стандарта, т.к. всякие aliasing-и и UB внутри компилятора они пока не могут себе позволить
Отредактировано 18.11.2019 14:00 Videoman . Предыдущая версия . Еще …
Отредактировано 18.11.2019 13:59 Videoman . Предыдущая версия .
Re: constexpr и преобразование указателей
От: Vamp Россия  
Дата: 18.11.19 14:41
Оценка: 18 (1)
Да. Преобразование void указателей — не core constant expression. См. https://en.cppreference.com/w/cpp/language/constant_expression
Да здравствует мыло душистое и веревка пушистая.
Re[7]: constexpr и преобразование указателей
От: wl. Россия  
Дата: 18.11.19 14:47
Оценка:
Здравствуйте, Videoman, Вы писали:


V>У меня рабочая версия VS 2017 — 15.9.17 (последняя) — на ней не работает. На 19-ой под https://gcc.godbolt.org/ — вроде как работает, но к сожалению это пока не мой вариант


на 19-й тоже не работает, ни v142, ни llvm
Re[8]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 14:50
Оценка:
Здравствуйте, wl., Вы писали:

wl.>на 19-й тоже не работает, ни v142, ни llvm


А как тогда вот это понимать ?
Re[2]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 14:54
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Да. Преобразование void указателей — не core constant expression. См. https://en.cppreference.com/w/cpp/language/constant_expression


Это как бы уже поняли, а как тогда строить дизайн класса, типа как я привел? Как двигать указатель, никак?
Re[5]: constexpr и преобразование указателей
От: B0FEE664  
Дата: 18.11.19 14:55
Оценка:
Здравствуйте, Videoman, Вы писали:

V>У меня не пашет под VS 2017. Выдает такое:

V>

V>d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(74): error C2131: expression did not evaluate to a constant
V>d:\development\hopsteam\ingest\trunk\htcapture\src\main.cpp(54): note: failure was caused by a conversion from void* to a pointer-to-object type

А если сделать грубо:
    static constexpr uint8_t* bytes(const void* data) { return (uint8_t*)((const ptrdiff_t)const_cast<void*>(data)); }

?
И каждый день — без права на ошибку...
Re[6]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 15:08
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>А если сделать грубо:

BFE>
BFE>    static constexpr uint8_t* bytes(const void* data) { return (uint8_t*)((const ptrdiff_t)const_cast<void*>(data)); }
BFE>

BFE>?

Ой, чего я только не пытался сделать — всё таже самая ошибка: failure was caused by a conversion from void* to a pointer-to-object type
Собственно поэтому и пришлось задать вопрос в форуме, как народ решает эту проблему, проблема ли это, и что делать.
Re[3]: constexpr и преобразование указателей
От: Vamp Россия  
Дата: 18.11.19 15:52
Оценка:
V>Это как бы уже поняли, а как тогда строить дизайн класса, типа как я привел? Как двигать указатель, никак?

Дизайн КАКОГО класса? В том, что ты привел, есть приватный член и его инкремент, который совершенно непонятно для чего нужен. Если ты покажешь весь класс, станет понятнее, как его сделать, и для чего тебе инкременитровать void*.
В лоб задача нерешаема, по определению — на этапе компиляции никаких адресов нет, так что и преобразовать ничего невозможно.
Да здравствует мыло душистое и веревка пушистая.
Re[9]: constexpr и преобразование указателей
От: rg45 СССР  
Дата: 18.11.19 18:49
Оценка: 6 (1)
Здравствуйте, Videoman, Вы писали:

wl.>>на 19-й тоже не работает, ни v142, ни llvm


V>А как тогда вот это понимать ?


Я тоже только что попробовал: самая последняя студия — 2019(16.3.9), самый последний тулсет (v142), самый последний стандарт (Preview — Features from the Latest C++ Working Draft (std:c++latest)) — все та же ошибка. Похоже, так тому и быть.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 18.11.2019 18:51 rg45 . Предыдущая версия .
Re[10]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 19:53
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я тоже только что попробовал: самая последняя студия — 2019(16.3.9), самый последний тулсет (v142), самый последний стандарт (Preview — Features from the Latest C++ Working Draft (std:c++latest)) — все та же ошибка. Похоже, так тому и быть.


Спасибо за попытку! Если вдруг случайно появится решение проблемы, прошу дать знать
Re[4]: constexpr и преобразование указателей
От: Videoman Россия https://hts.tv/
Дата: 18.11.19 20:38
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Дизайн КАКОГО класса? В том, что ты привел, есть приватный член и его инкремент, который совершенно непонятно для чего нужен. Если ты покажешь весь класс, станет понятнее, как его сделать, и для чего тебе инкременитровать void*.

V>В лоб задача нерешаема, по определению — на этапе компиляции никаких адресов нет, так что и преобразовать ничего невозможно.

Ок. У меня со всеми библиотеками уже лет 10 используется такой класс — идея подсмотрена в ASIO:
    class MutableBuffer
    {
    public:

        using pointer_type = void*;

        constexpr MutableBuffer() noexcept = default;
        constexpr MutableBuffer(const MutableBuffer& that) noexcept = default;
        constexpr MutableBuffer(MutableBuffer&& that) noexcept = default;
        constexpr MutableBuffer(const MutableBuffer& that, uint_t maxSize) noexcept;
        constexpr MutableBuffer(pointer_type data, uint_t size) noexcept;

        void swap(MutableBuffer& that) noexcept;
        constexpr MutableBuffer& operator=(const MutableBuffer& that) noexcept = default;
        constexpr MutableBuffer& operator=(MutableBuffer&& that) noexcept = default;

        MutableBuffer& operator+=(uint_t offset) noexcept; // ???
        MutableBuffer& operator-=(uint_t offset) noexcept; // ???

        bool operator==(const MutableBuffer& that) const;
        bool operator<(const MutableBuffer& that) const;

        constexpr pointer_type Data() const noexcept;
        constexpr uint_t Size() const noexcept;

    private:

        pointer_type m_data = nullptr;  // Buffer data reference
        uint_t m_size = 0;              // Buffer size
    };

    MutableBuffer operator+(const MutableBuffer& buffer, uint_t offset) noexcept;
    MutableBuffer operator+(uint_t offset, const MutableBuffer& buffer) noexcept;
    MutableBuffer operator-(const MutableBuffer& buffer, uint_t offset) noexcept;
    MutableBuffer operator-(uint_t offset, const MutableBuffer& buffer) noexcept;

Он используется во всех операциях ввода вывода, и не только. Прелесть его в том, что если в руках уже есть такой MutableBuffer, то, создавая на основе него другие MutableBuffer и используя только им предоставленные операции, мы можем только сужать диапазон исходного буфера, даже передавая туда неправильные размеры и смещения. При этом в своей реализации он совсем не использует исключения и на практике не вносит overhead при оптимизации.
На практике удалось заметить, что такой подход привел практически к полному исчезновению ошибок выхода за границы буферов чтения/записи, что не может не радовать
Теперь по мере переползания на C++17 решили добавить constexpr. Как видно, все основные операции, кроме operator+=() и operator-=(), могут быть реализованы на этапе компиляции.
consexpr MutableBuffer buffer(...); // Original buffer
// ....
consexpr MutableBuffer trimmed_right(buffer, buffer.Size() - 1);
consexpr MutableBuffer trimmed_left(buffer + 1); // ???

Вот такая асимметрия мне и не нравится.
Re[2]: constexpr и преобразование указателей
От: rg45 СССР  
Дата: 19.11.19 10:28
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Да. Преобразование void указателей — не core constant expression. См. https://en.cppreference.com/w/cpp/language/constant_expression


Пункт 14 — как раз оно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.