Search code examples
c++constexprc++23constexpr-functionc++26

How can I get a constexpr size from a std::set, which I can use to return a std::array with the number of elements in the std::set in C++23?


How can I get a constexpr size from a std::set, which I can use to return a std::array with the number of elements in the std::set in C++23 or to-be C++26, as far as supported by either G++ oder Clang?

The use-case is a constexpr parser, which returns a "complex" object, having a flexible number of elements of various kinds in it.

I saw Can I make a constexpr object of std::set?, which seemed to go in a similar direction, but I replaced std::set already by frozen::set in the return value. However, somehow I have to determine the size parameter of it.

frozen BTW is from https://github.com/serge-sans-paille/frozen .

Because std::set does not have a constexpr size method, I tried to put a std::vector in between, which apparently does not help.

I have some simplified code:

#include <algorithm>
#include <array>
#include <set>
#include <string_view>
#include <vector>
#include <frozen/set.h>
#include <frozen/bits/basic_types.h>

using namespace std::string_view_literals;

constexpr auto test(const std::string_view input) {
        std::set<std::string_view> my_set;
        my_set.emplace(input); // For sure the reality is more complicated and can lead to a flexible number of elements
        constexpr std::vector<std::string_view> my_vec(my_set.begin(), my_set.end());
        constexpr std::array<std::string_view, my_vec.size()> my_arr{};
        std::copy_n(my_vec.begin(), my_vec.size(), my_arr.begin());
        constexpr frozen::bits::carray<std::string_view, my_vec.size()> my_carr(my_arr);
        return frozen::set<std::string_view, my_vec.size()>(my_carr);
}

int main() {
        constexpr auto frozen_set = test("test"sv);
        return 0;
}

Even when I use a development version of G++ (14.0.1), I get the following error:

test.cpp: In function ‘constexpr auto test(std::string_view)’:
test.cpp:14:68: error: temporary of non-literal type ‘std::set<std::basic_string_view<char> >::iterator’ {aka ‘std::_Rb_tree<std::basic_string_view<char>, std::basic_string_view<char>, std::_Identity<std::basic_string_view<char> >, std::less<std::basic_string_view<char> >, std::allocator<std::basic_string_view<char> > >::const_iterator’} in a constant expression
   14 |         constexpr std::vector<std::string_view> my_vec(my_set.begin(), my_set.end());
      |                                                        ~~~~~~~~~~~~^~
In file included from /usr/include/c++/14/set:62,
                 from test.cpp:3:
/usr/include/c++/14/bits/stl_tree.h:324:12: note: ‘std::_Rb_tree_const_iterator<std::basic_string_view<char> >’ is not literal because:
  324 |     struct _Rb_tree_const_iterator
      |            ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/14/bits/stl_tree.h:324:12: note:   ‘std::_Rb_tree_const_iterator<std::basic_string_view<char> >’ is not an aggregate, does not have a trivial default constructor, and has no ‘constexpr’ constructor that is not a copy or move constructor
test.cpp:16:59: error: request for member ‘begin’ in ‘my_arr’, which is of non-class type ‘const int’
   16 |         std::copy_n(my_vec.begin(), my_vec.size(), my_arr.begin());
      |                                                           ^~~~~

If the error in line 14 is resolved, probably I get the rest done myself.


Solution

  • You cannot use std::set in constant expressions because none of its member functions are marked constexpr.

    As a workaround, you can create a std::vector and "set-ify" it by sorting and removing duplicate elements:

    template <typename T>
    constexpr void setify(std::vector<T>& v) {
        std::ranges::sort(v);
        // Eliminate non-unique elements, which generates some garbage towards the end.
        auto one_past_unique_elements = std::ranges::unique(v).begin();
        // Reduce the size of the vector to get rid of the garbage.
        v.erase(one_past_unique_elements, v.end());
    }
    

    See also What's the most efficient way to erase duplicates and sort a vector?

    Also note that when you're subsequently turning the vector into an array, you'll need two separate constant expressions (see Can you convert std::vector to std::array at compile-time without making the vector twice?) unless you have an upper bound for the size of the vector.


    Note: To my knowledge, there aren't any proposals for C++26 that would add constexpr functionality to associative containers. I'm sure there are some implementation difficulties with that, such as the fact that some standard libraries keep an "end node" directly in the container. Maybe it's possible after P2738R1: constexpr cast from void*.