Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr vector push_back or how to constexpr all the things

There is a good talk by Jason Turner and Ben Deane from C++Now 2017 called "Constexpr all the things" which also gives a constexpr vector implementation. I was dabbling with the idea myself, for educational purposes. My constexpr vector was pure in the sense that pushing back to it would return a new vector with added element.

During the talk, I saw a push_back implementation tat looks like more or less following:

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

They were taking the element by value and moving it but, I don't think this is the source of my problems. The thing I want to know is, how this function could be used in a constexpr context? This is not a const member function, it modifies the state. I don think it is possible to do something like

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

And if this is not possible, how could we use this thing in constexpr context and achieve the goal of the task using this vector, namely compile-time JSON parsing?

Here is my version, so that you can see both my new vector returning version and the version from the talk. (Note that performance, perfect forwarding etc. concerns are omitted)

#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
} 

So, this thing made me realize there is probably something deep that I don' know about constexpr. So, recapping the question; how such a constexpr vector could offer a mutating push_back like that in a constexpr context? Seems like it is not working in a constexpr context right now. If push_back in a constexpr context is not intended to begin with, how can you call it a constexpr vector and use it for compile-time JSON parsing?

like image 605
meguli Avatar asked Oct 18 '25 23:10

meguli


1 Answers

Your definition of vector is correct, but you can't modify constexpr objects. They are well and truly constant. Instead, do compile-time calculations inside constexpr functions (the output of which can then be assigned to constexpr objects).

For example, we can write a function range, which produces a vector of numbers from 0 to n. It uses push_back, and we can assign the result to a constexpr vector in 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);
}
like image 63
Alecto Irene Perez Avatar answered Oct 22 '25 04:10

Alecto Irene Perez