constexpr вектор push_back или как constexpr все вещи - PullRequest
1 голос
/ 30 апреля 2019

Есть хороший доклад Джейсона Тернера и Бена Дина из C ++ Now 2017 под названием constexpr All the things, который также дает constexpr векторную реализацию. Я баловался с этой идеей сам, в образовательных целях. Мой вектор constexpr был чист в том смысле, что возвращение к нему вернуло бы новый вектор с добавленным элементом.

Во время разговора я увидел, что реализация push_back выглядит примерно так:

constexpr void push_back(T const& e) {
    if(size_ >= Size)
       throw std::range_error("can't use more than Size");
    else {
        storage_[size_++] = e;
    }
} 

Они брали элемент по значению и перемещали его, но я не думаю, что это источник моих проблем. Я хочу знать, как эту функцию можно использовать в контексте constexpr? Это не постоянная функция-член, она изменяет состояние. Я не думаю, что можно сделать что-то вроде

constexpr cv::vector<int> v1;
v1.push_back(42);

И если это невозможно, как мы могли бы использовать эту вещь в контексте constexpr и достичь цели задачи, используя этот вектор, а именно анализ JSON во время компиляции?

Вот моя версия, так что вы можете увидеть как мою новую версию, возвращающую вектор, так и версию из беседы. (Обратите внимание, что вопросы производительности, идеальной пересылки и т. Д. Не учитываются)

#include <cstdint>
#include <array>
#include <type_traits>

namespace cx {
template <typename T, std::size_t Size = 10>
struct vector {
    using iterator = typename std::array<T, Size>::iterator;
    using const_iterator = typename std::array<T, Size>::const_iterator;

    constexpr vector(std::initializer_list<T> const& l) {
        for(auto& t : l) {
            if(size_++ < Size)
                storage_[size_] = std::move(t);
            else
                break;
        }
    }

    constexpr vector(vector const& o, T const& t) {
        storage_ = o.storage_;
        size_ = o.size_;
        storage_[size_++] = t;
    }

    constexpr auto begin() const { return storage_.begin(); }
    constexpr auto end()  const { return storage_.begin() + size_; }
    constexpr auto size() const { return size_; }

    constexpr void push_back(T const& e) {
        if(size_ >= Size)
            throw std::range_error("can't use more than Size");
        else {
            storage_[size_++] = e;
        }
    }

    std::array<T, Size> storage_{};
    std::size_t size_{};
};
}

template <typename T>
constexpr auto make_vector(std::initializer_list<T> const& l) {
    return cx::vector<int>{l};
}

template <typename T>
constexpr auto push_back(cx::vector<T> const& o, T const& t) {
    return cx::vector<int>{o, t};
}

int main() {
    constexpr auto v1 = make_vector({1, 2, 3});
    static_assert(v1.size() == 3);
    constexpr auto v2 = push_back(v1, 4);
    static_assert(v2.size() == 4);

    static_assert(std::is_same_v<decltype(v1), decltype(v2)>);

    // v1.push_back(4); fails on a constexpr context
} 

Итак, это заставило меня понять, что, вероятно, что-то глубокое, что я не знаю о constexpr. Итак, повторяем вопрос; как такой вектор constexpr мог предложить мутирующий push_back в контексте constexpr? Похоже, это не работает в контексте constexpr прямо сейчас. Если push_back в контексте constexpr не предназначен для начала, как вы можете назвать его вектором constexpr и использовать его для анализа JSON во время компиляции?

1 Ответ

5 голосов
/ 30 апреля 2019

Ваше определение вектора правильное, но вы не можете изменять constexpr объекты. Они хорошо и действительно постоянны. Вместо этого выполните вычисления во время компиляции внутри функций constexpr (выходные данные которых затем можно назначить объектам constexpr).

Например, мы можем написать функцию range, которая создает вектор чисел от 0 до n. Он использует push_back, и мы можем присвоить результат вектору constexpr в main.

constexpr vector<int> range(int n) {
    vector<int> v{};
    for(int i = 0; i < n; i++) {
        v.push_back(i); 
    }
    return v; 
}

int main() {
    constexpr vector<int> v = range(10);
}
...