Я пытаюсь создать класс записи, который будет использоваться для преобразования данных в двоичный поток во время компиляции с использованием 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?
Обновлено: опубликовано два похожих вопроса, но они не совсем то, что здесь задают, а есть вопросы и различия;
you need to wait for c++20
).Там, где, как и в этом вопросе, используется C++20, таким образом, std::vector поддерживается в контексте constexpr. И выделенная память также освобождается во время компиляции.
Итак, вопрос скорее в следующем; Как сохранить данные std::vector во время выполнения с помощью std::array?
Фундаментальный вопрос заключается в том, что переменная constexpr
сама должна иметь постоянную инициализацию, независимо от контекста, в котором она появляется. И при постоянной оценке этой инициализации в конце не может остаться выделенного динамического выделения.
возможно дубликат stackoverflow.com/questions/69498115/…
Или stackoverflow.com/questions/72415239/…
Кроме того, consteval
, вероятно, здесь некорректно. constexpr
вместо этого подойдет.
См. godbolt.org/z/n6K61jEhv исправление, которое я предлагаю.
См. этот ответ в обмане, в котором говорится: «std::vector использует динамическое распределение памяти. Оператор new нельзя использовать в методе constexpr»
Проблема в том, что для того, чтобы функция была 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
во время компиляции.
Вы были абсолютно правы - я не знал ситуации с распределением и освобождением времени компиляции. Кроме того, как вы упомянули, мой компилятор не разрешил даже создание класса Writter в функции toConstant(), предположительно из-за некоторого выделения памяти в std::vector. Я переписал свой ответ.
Выносим на свет очень полезные комментарии пользователя 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, и, пожалуйста, если я объяснил что-то не так, оставьте комментарий.
wr
иvec
не должны быть переменнымиconstexpr
.std::vector
нельзя использовать как часть переменнойconstexpr
. НапишитеtoConstant().getVector().size()
напрямую, чтобы получить размер как переменнуюconstexpr
, и вообще не создавайтеvec
переменнуюconstexpr
. (toConstant()
необходимо вызывать дважды: один раз для размера и один раз для данных. Обойти это невозможно.)