1

I have a data structure defined and initialized similar to the following:

#include <vector>
#include <array>

struct SomeStruct {
    std::vector<int> vec;
};

int main() {
    std::array<SomeStruct, 2> arr {
        SomeStruct {
            .vec = {
                1, 2
            }
        },
        SomeStruct {
            .vec = {
                3, 4, 5
            }
        }
    };
}

This compiles correctly, but since the entire structure is known at compile time, I tried to make it a constexpr.

Simply declaring arr as constexpr in the previous example results in an error:

main.cpp: In function ‘int main()’:
main.cpp:20:5: error: the type ‘const std::array’ of constexpr variable ‘arr’ is not literal
     };
     ^
In file included from main.cpp:2:0:
/usr/include/c++/7/array:94:12: note: ‘std::array’ is not literal because:
     struct array
            ^~~~~
/usr/include/c++/7/array:94:12: note:   ‘std::array’ has a non-trivial destructor

I'm guessing this is because std::vector does not have a constexpr constructor/destructor.

I then tried using an std::array with a template on the containing struct:

#include <array>

template <int N>
struct SomeStruct {
    std::array<int, N> vec;
};

int main() {
    constexpr std::array<SomeStruct, 2> arr {
        SomeStruct<2> {
            .vec = {
                1, 2
            }
        },
        SomeStruct<3> {
            .vec = {
                3, 4, 5
            }
        }
    };
}

This results in an error too:

main.cpp: In function ‘int main()’:
main.cpp:10:39: error: type/value mismatch at argument 1 in template parameter list for ‘template struct std::array’
     constexpr std::array<SomeStruct, 2> arr {
                                       ^
main.cpp:10:39: note:   expected a type, got ‘SomeStruct’
main.cpp:10:41: error: scalar object ‘arr’ requires one element in initializer
     constexpr std::array<SomeStruct, 2> arr {
                                         ^~~

But I cannot give SomeStruct the template parameter because the sizes can differ.

What is the best way to define a constexpr data structure like this?

2
  • Which compiler are you using? I get errors that SomeStruct is missing its template parameter in clang. In g++, I get the error you do (so I assume that's what you use), but then I put in the template parameter for SomeStruct and that error goes away (but then I have a mismatch between the two elements, because one is size 2 and the other 3). godbolt.org/z/lQp3HT Commented Nov 5, 2019 at 11:49
  • @ChrisMM Yes, I'm using GCC. And I you're right, there's a different error in my code but it can't be solved without causing the error you see. Updated question to clarify. Commented Nov 5, 2019 at 11:52

2 Answers 2

3

Like said here

Because std::array<T, N> is an aggregate, it can be initialized as a constexpr if and only if the underlying type T has a constexpr constructor (when presented with each initializer you provide).

As std::vector doesn't have a constexpr constructor (yet), this will not work.

So with pre-C++20 this will not work with dynamic size STL containers. No solution or quick fix. Don't get your hopes up.

The alternative is to design your own constexpr fixed-max-size Vector class. e.g.

template <typename T, std::size_t N>
class Vector {
private:
   T values[N]{};
public:
   std::size_t size{ 0 };
   constexpr bool empty() const noexcept { return size == 0; }
   constexpr void clear() noexcept { size = 0; }

   constexpr T* begin() noexcept { return &values[0]; }
   constexpr T* end() noexcept { return &values[size]; }
   constexpr T const* cbegin() const noexcept { return &values[0]; }
   constexpr T const* cend() const noexcept { return &values[size]; }

   constexpr T& back() noexcept { return values[size - 1]; }

   constexpr T operator[] (int const loc) const noexcept { return values[loc]; }
   constexpr T& operator[] (int const loc) noexcept { return values[loc]; }

   constexpr void push_back(T const& value) noexcept { values[size++] = value; }

   constexpr void resize(int const newSize) noexcept {
       auto const diff = newSize - size;
       if (diff > 0) memset(end(), 0, diff * sizeof(T));
       size = newSize;
   }
};

This is one I use sometimes... you need to add a initializer_list constructor.

edit: Quick test... this seems to compile.

#include <array>

template <typename T, std::size_t N>
class Vector {
private:
   T values[N]{};
public:
    std::size_t size{ 0 };
    constexpr Vector(std::initializer_list<T> il) noexcept
     : size(std::distance(std::cbegin(il), std::cend(il)))
     {
         std::size_t i = 0;
         for(auto it = std::cbegin(il); it != std::cend(il); ++it) {
             values[i++]=*it;
         }
     }
};

struct SomeStruct {
    Vector<int,5> vec;
};

int main() {
    [[maybe_unused]]constexpr std::array<SomeStruct, 2> arr {
        SomeStruct {
            .vec = {
                1, 2
            }
        },
        SomeStruct {
            .vec = {
                3, 4, 5
            }
        }
    };
}
Sign up to request clarification or add additional context in comments.

7 Comments

I know it won't work, I addressed this in my question. I'm looking for a suggestion of something that will work.
@Flau whoah there! just trying to help... sometime reality is disappointing. updated my answer.
Sorry, someone had already posted and deleted an answer telling me vector doesn't have a constexpr constructor; thanks for the update.
According to this list, the constexpr vector paper hasn't been adopted for C++20.
Accepting this because it keeps the usage syntax the same. It's a shame there isn't currently a nicer way to do this (with std::array or otherwise), but hopefully C++20 will help with that.
|
2

If you store the rows separately, then you can have an array of spans. You must keep the rows alive at least as long as the parent array. In this example that is achieved by them being variables in same scope:

struct SomeStruct {
    std::span<int> vec;
};

std::array arr1 {1, 2};
std::array arr2 {3, 4, 5};
constexpr std::array arr {
    SomeStruct { .vec = arr1 },
    SomeStruct { .vec = arr2 },
};

Or, non-mutable version with more constexpr:

struct SomeStruct {
    std::span<const int> vec;
};

constexpr std::array arr1 {1, 2};
constexpr std::array arr2 {3, 4, 5};
constexpr std::array arr  {
    SomeStruct { .vec = arr1  },
    SomeStruct { .vec = arr2  },
};

3 Comments

Uhm... std::span is C++20, so not officially released... this will not work with C++17... plus span will be a reference class/viewer, so if arr1 and/or arr2 are destroyed, arr will be invalided. Or I'm not understanding it. That's the problem with C++20... not officially out, so...
@JHBonarius Designated initialiser lists are also C++20, and OP uses those. You can always use a non-standard implementation of span instead; it's implementable without C++20. You must keep the rows alive at least as long as the parent array.
@JHBonarius yah, my fault for adding some constexpr without testing. The rows can be constexpr only if the member span is to const int.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.