Класс времени компиляции C++ с переменной-членом std::vector и использование ее данных во время выполнения

Я пытаюсь создать класс записи, который будет использоваться для преобразования данных в двоичный поток во время компиляции с использованием std::vector, а затем преобразования его в std::array для использования во время выполнения. например:

#include <cstdint>
#include <array>
#include <vector>

class Writter
{
   private:
    std::vector<uint8_t> vec;

   public:
    constexpr Writter() {}

    constexpr const std::vector<uint8_t> getVector() const { return (vec); }

    constexpr void write(uint8_t val) { vec.push_back(val); }

    constexpr size_t getVecSize() const { return (vec.size()); }
};

consteval Writter toConstant()
{
    Writter wr;

    //wr.write(1);

    return(wr);
}

consteval auto getBinary()
{
    constexpr Writter               wr = toConstant();
    constexpr std::vector<uint8_t>  vec = wr.getVector();
    constexpr size_t                vecSize = vec.size();
    std::array<uint8_t, vecSize>    arr;

    for(size_t i = 0; i < vecSize; ++i)
    {
        arr[i] = vec[i];
    }
    
    return arr;
}

int main()
{
    constexpr auto binary = getBinary();
}

Текущая версия компилируется, но как только вы попытаетесь использовать функцию-член wr.write(1), компиляция завершится неудачей с указанным выводом:

/opt/compiler-explorer/gcc-trunk-20240725/include/c++/15.0.0/bits/allocator.h:193:52: error: 'toConstant()()' is not a constant expression because it refers to a result of 'operator new'
  193 |             return static_cast<_Tp*>(::operator new(__n));
      |                                      ~~~~~~~~~~~~~~^~~~~
<source>:39:12: error: invalid types 'int[size_t {aka long unsigned int}]' for array subscript
   39 |         arr[i] = vec[i];

Я пытался использовать разные контейнеры или просто иметь необработанный указатель и самостоятельно управлять памятью, но проблема всегда заключается в том, что компилятор не понимает, что переменная-член std::vector (или любой другой контейнер) может быть оценена. во время компиляции. Есть ли способ заставить компилятор понять, что он находится в контексте constexpr?

Обновлено: опубликовано два похожих вопроса, но они не совсем то, что здесь задают, а есть вопросы и различия;

  • Невозможно создать constexpr std::vector: этот вопрос касается отсутствия поддержки std::vector до C++20 (и ответ в основном был; you need to wait for c++20).
  • Вектор и строка constexpr C++20 не работают: этот вопрос касается использования std::vector и std::string, время жизни которых сохраняется до времени выполнения, что невозможно, поскольку любая динамическая память, выделяемая во время выполнения , также необходимо освободить во время выполнения, чего не происходило в коде OP.

Там, где, как и в этом вопросе, используется C++20, таким образом, std::vector поддерживается в контексте constexpr. И выделенная память также освобождается во время компиляции.

Итак, вопрос скорее в следующем; Как сохранить данные std::vector во время выполнения с помощью std::array?

wr и vec не должны быть переменными constexpr. std::vector нельзя использовать как часть переменной constexpr. Напишите toConstant().getVector().size() напрямую, чтобы получить размер как переменную constexpr, и вообще не создавайте vec переменную constexpr. (toConstant() необходимо вызывать дважды: один раз для размера и один раз для данных. Обойти это невозможно.)
user17732522 25.07.2024 19:44

Фундаментальный вопрос заключается в том, что переменная constexpr сама должна иметь постоянную инициализацию, независимо от контекста, в котором она появляется. И при постоянной оценке этой инициализации в конце не может остаться выделенного динамического выделения.

user17732522 25.07.2024 19:47

возможно дубликат stackoverflow.com/questions/69498115/…

康桓瑋 25.07.2024 19:48

Или stackoverflow.com/questions/72415239/…

康桓瑋 25.07.2024 19:48

Кроме того, consteval, вероятно, здесь некорректно. constexpr вместо этого подойдет.

user17732522 25.07.2024 19:50

См. godbolt.org/z/n6K61jEhv исправление, которое я предлагаю.

user17732522 25.07.2024 19:59

См. этот ответ в обмане, в котором говорится: «std::vector использует динамическое распределение памяти. Оператор new нельзя использовать в методе constexpr»

user12002570 26.07.2024 13:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
101
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема в том, что для того, чтобы функция была consteval, любая выделенная память должна быть освобождена внутри этой функции. Казалось бы, ваш компилятор не выделяет никакой памяти при объявлении вектора, но он явно должен сделать это, когда вы вызываете wr.write(1). Кажется, что память не освобождается внутри toConstant(), возможно, потому, что wr перемещается, а не копируется, или, возможно, по другой причине. Это значит, что toConstant() не consteval.

Чтобы это заработало, вы можете перенести создание объекта Writter в getBinary(). Это означает, что память будет освобождена внутри функции, в которой она была выделена.

Обратите внимание, что вы также должны установить vecSize в качестве константы времени компиляции. Мой компилятор не принял использование std::vector::size() для указания размера std::array.

Следующее определение функции скомпилировано для меня.

consteval auto getBinary()
{
    Writter                         wr;
    wr.write(1);
    std::vector<uint8_t>            vec = wr.getVector();
    constexpr size_t                vecSize = 1;
    std::array<uint8_t, vecSize>    arr;

    for (size_t i = 0; i < vecSize; ++i)
    {
        arr[i] = vec[i];
    }

    return arr;
}

Нет никакой гарантии, что пустой std::vector не будет выделен. Выделение в куче во время компиляции допустимо, если оно снова освобождается до окончания константного выражения. Нет проблем с доступом к массиву constexpr во время компиляции.

user17732522 25.07.2024 19:56

Вы были абсолютно правы - я не знал ситуации с распределением и освобождением времени компиляции. Кроме того, как вы упомянули, мой компилятор не разрешил даже создание класса Writter в функции toConstant(), предположительно из-за некоторого выделения памяти в std::vector. Я переписал свой ответ.

Phil Rosenberg 26.07.2024 12:16
Ответ принят как подходящий

Выносим на свет очень полезные комментарии пользователя user17732522;

Проблема заключается в том, что std::vector, хотя его и можно использовать в контексте constexpr, он сам по себе не может быть объявлен как constexpr или класс, который его содержит. (то же самое касается std::string и всего, что использует динамическое распределение памяти)

Таким образом, функцию toConstant() необходимо вызвать дважды: один раз для получения вектора (не как constexpr) и один раз для получения размера вектора (как constexpr).

Сам класс правильный, только getBinary() нужно изменить на что-то вроде этого:

consteval auto getBinary()
{
    std::vector<uint8_t>            vec = toConstant().getVector();
    constexpr size_t                vecSize = toConstant().getVector().size();
    std::array<uint8_t, vecSize>    arr;

    for(size_t i = 0; i < vecSize; ++i)
    {
        arr[i] = vec[i];
    }
    
    return arr;
}

Еще раз спасибо пользователю 17732522, и, пожалуйста, если я объяснил что-то не так, оставьте комментарий.

Другие вопросы по теме

Gcc и clang не согласны с использованием шаблонов псевдонимов в качестве аргумента шаблона шаблона
Настройка libstdc++ для данной версии gcc
Почему я получаю сообщение «объявление for не относится к классу, шаблону класса или частичной специализации шаблона класса» в Clang, а не в GCC
GCC 14 «возможно, висячая ссылка на временное» предупреждение или нет, в зависимости от аргумента функции
Почему исключение не может быть перехвачено в Windows с помощью msvc, а в Linux - с помощью GCC
Ограничение встроенной сборки gnu `i` для адреса памяти
Могу ли я перезаписать malloc, вызываемый библиотечными функциями?
Есть ли способ явно указать компилятору остановить оптимизацию?
Неоднозначное разрешение шаблонов функций-членов и функций, не являющихся членами C++, в GCC 14, но не в предыдущих версиях GCC
Как отладить «*** обнаружено переполнение буфера ***: имя_программы прекращено» вместе с _FORTIFY_SOURCE=2