1

I'm trying to make a wrapper of std::array to perform boundary checks as suggested by this answer. This is the code I have:

template <typename T, size_t N>
class Array : private std::array<T, N> {
public:
    using std::array<T, N>::array;

    T operator[](size_t i) {
        return this->at(i);
    }

    T operator[](size_t i) const {
        return this->at(i);
    }
};

int main() {
    Array<int, 3> arr = {0,0,0};
}

When I try to run it I get the following error: error: no matching constructor for initialization of 'Array<int, 3>'.

If I omit the line using std::array<T, N>::array; and inherit it publicly the code works, though this option is not advisable.

What am I missing here? Why can't my class create an instance like this?

Thanks in advance!

gustavo
  • 13
  • 5
  • Rather than inheriting from `std::array`, consider enabling bounds checking for it, if your standard library supports it. The error message seems to come from Clang; if you use it with libstdc++, the correct flag is `-D_GLIBCXX_DEBUG`. – HolyBlackCat Jun 03 '20 at 17:11
  • 2
    `array` doesn't actually have a constructor which takes an `initializer_list`. – ChrisMM Jun 03 '20 at 17:11
  • 1
    To clarify: it uses `aggregate initialization` instead. – ChrisMM Jun 03 '20 at 17:13
  • @HolyBlackCat This approach seems more elegant to me as I don't rely on different std library versions... @ChrisMM I understand and will edit the question. I still don't get why my class can't preform `aggregate_initialization` if inherited that way, though. – gustavo Jun 03 '20 at 17:22
  • @gustavo, see the second part of my answer below. – ChrisMM Jun 03 '20 at 17:23

2 Answers2

3

The std::array struct does not implement a constructor which takes an initializer_list. It actually only has an implicitly defined constructor. std::array, per [array.cons], does meet the conditions for aggregate though, so it can be initialized via {1,2,3} for example.

The requirements for aggregates specified (from [dcl.init.aggr]/1.4)

no virtual, private, or protected base classes

Therefore, your class will not work with a private base class.

Note that even if you make the base class public, you end up violating [dcl.init.aggr]/1.1, which states

no user-provided, explicit, or inherited constructors

So you would have to get rid of your using statement.

See here for a working example.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
2

std::array is designed to be an aggregate. It has no user-provided constructors, so one can initialize it using aggregate initialization. Because your Array class has a private base class, it is not an aggregate, and can only be initialized by a constructor.

Another way of looking at it is that since Array has members that are hidden from the user, it does not make sense for the language to allow the user to directly initialize those elements using the aggregate syntax, the way one might initialize a normal array. Instead the user must call a constructor, wherein the Array class's author has explicitly implemented the necessary initialization logic to fulfill the Array class's contract.

One simple solution is to make the std::array base class public. If you don't want to do that, you can write your own initializer_list constructor, but it's tricky and imperfect:

// delegate to a helper constructor
Array(std::initializer_list<T> il) : Array(il, std::make_index_sequence<N>{}) {}

private:
template <size_t... i>
Array(std::initializer_list<T> il, std::index_sequence<i...>)
  : std::array<T, N>{(i < il.size() ? il.begin()[i] : T{})...} {}

The helper constructor uses an element from the initializer list to initialize the corresponding std::array element, if one exists; otherwise, it initializes it from T{}.

The main problem with this is that if T is a class that cannot be value-initialized, then this Array constructor cannot be called even if N initializers are supplied, because the compiler cannot enforce the "il contains N elements" condition at compile time, and thus must assume that T{} may be called at runtime. There is no way to perfectly emulate aggregate initialization.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312