Search code examples
c++constexprstd-rangesc++23

How to get random access container with std-ranges at compile time?


I have the following code:

#include <ranges>
#include <algorithm>

auto func(const char* Str) {
    return std::string_view(Str) 
        | std::views::split(',')
        | std::views::transform([](auto inp) { return inp.data(); })
        | std::ranges::to<std::vector<const char *>>();
}

Note I know that the result of func(test), where test = "a,b" would be to pointers pointing into test and thus printing them would result in ["a,b", "b"] but I am only concerned with the beginning of the string anyways.

I tried to make this function consteval, as i saw that all the std::ranges functions are allconstexpr. This was my attempt:

template<const char * Str>
consteval auto func() {
    return std::string_view(Str)
        | std::views::split(',')
        | std::views::transform([](auto inp) { return inp.data(); })
        | std::ranges::to<std::vector<const char *>>();
}

But I get the error (See demo)

error: call to consteval function 'func<test>' is not a constant expression
note: pointer to subobject of heap-allocated object is not a constant expression

where test is a "string" of type static constexpr char[]. I know that this error stems from std::vector allocating memory, but I thought std::vector supports constexpr. Though maybe it is the problem of std::ranges::to which does not treat the destination container type as constexpr.

I have also tried to use std::ranges::to<std::array> but to did not support that either. Is there another container which supports random access and constexpr which works with std::ranges::to?


Solution

  • You can turn the transient dynamic allocations into a static/automatic allocation, taking advantage of the fact that vector_returning_function().size() can be a constant expression (because the vector dies and returns its memory):

    https://godbolt.org/z/MKser4ses

    consteval auto vector_to_array(auto f) {
        static constexpr std::size_t size = f().size();
        return [f]<std::size_t... I>(std::index_sequence<I...>) mutable {
            return std::array<std::ranges::range_value_t<decltype(f())>, size>{{ f()[I]... }};
        }(std::make_index_sequence<size>{});
    }
    

    You call it with a captureless lambda that returns the vector instead.