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?

flau
  • 1,328
  • 1
  • 20
  • 36
  • 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). https://godbolt.org/z/lQp3HT – ChrisMM Nov 05 '19 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. – flau Nov 05 '19 at 11:52

2 Answers2

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
            }
        }
    };
}
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
JHBonarius
  • 10,824
  • 3
  • 22
  • 41
  • I know it won't work, I addressed this in my question. I'm looking for a suggestion of something that _will_ work. – flau Nov 05 '19 at 12:13
  • @Flau whoah there! just trying to help... sometime reality is disappointing. updated my answer. – JHBonarius Nov 05 '19 at 12:22
  • Sorry, someone had already posted and deleted an answer telling me vector doesn't have a constexpr constructor; thanks for the update. – flau Nov 05 '19 at 12:23
  • 1
    According to [this list](http://93.90.116.65/JTC1/SC22/WG21/docs/papers/2019/sd-1.htm), the constexpr vector paper hasn't been adopted for C++20. – eerorika Nov 05 '19 at 12:41
  • @eerorika it was under debate until the bitter end... but AFAIK it will be in C++20 – JHBonarius Nov 05 '19 at 12:50
  • @JHBonarius Good to hear. I guess the list isn't up to date then. – eerorika Nov 05 '19 at 13:00
  • 1
    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. – flau Nov 05 '19 at 13:27
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  },
};
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    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 Nov 05 '19 at 13:02
  • @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. – eerorika Nov 05 '19 at 13:03
  • @JHBonarius yah, my fault for adding some constexpr without testing. The rows can be constexpr only if the member span is to const int. – eerorika Nov 05 '19 at 13:11