I'd like to wrap std::array to create a circular array but how to provide aggregate initialization

Viewed 253

With std::array you can do

std::array<int,3> v = {1,2,3};

But I want to provide a wrapper

template <typename T, int n> circular_array {
   auto operator[](size_t i){
       return m_data[i%n];  
   }
   // TODO forward all other methods directly except ``at`` which we don't need
 private:
   std::array<t,n> m_data;
}

but I lose the aggregate initialization that I had above for std::array. This will not work.

circular_array<int,3> v = {1,2,3};

though if I made m_data public I could do

circular_array<int,3> v = {{1,2,3}};

which is not desired.

Is there a way to achieve circular_array as a drop in for std::array including aggregate initialization?

2 Answers

Not sure if it's exactly what you're looking for, but you can use inheritance (that is derive your class from std::array) rather than composition (i.e. having a std:array data member).

Here's a demo:

#include <array>
#include <iostream>

template <typename T, int n>
class circular_array : public std::array<T, n>
{
public:
    auto operator[](size_t i) {
        return std::array<T, n>::data()[i % n];
    }
};

int main()
{
    circular_array<int, 3> v = {1,2,3};
    for (size_t i = 0; i < 10; ++i) {
        std::cout << v[i] << std::endl;
    }
    return 0;
}

I have tested this code in Visual Studio (Windows) and it works as expected; the native MSVC compiler gives no diagnostic (even in code analysis mode) but clang-cl issues the following for the aggregate initialization (which it doesn't give when using such syntax for a native std:array object):

warning : suggest braces around initialization of subobject [-Wmissing-braces]

Not being a Language-Lawyer, I'm not sure if this means that the circular_array<int, 3> v = {1,2,3}; line is truly still aggregate initialization; but, if it's not, then I'm equally unsure what it is.

The solution is to realize that std::array has to pull off the same trick. It does this by making it's single member public rather than hiding it.

msvc std::array

So the (partial) solution is

#include <array>
#include <iostream>

template <typename T, int n>
class circular_array 
{
private:
static
    constexpr int Wrap(int kX) noexcept
    {
        if (kX < 0)
            kX += n * (-kX / n + 1);

        return kX % n;
    }

public:
    typename std::array<T,n>::reference operator[](int i) {
        return m_data[Wrap(i)];
    }
    std::array<T,n> m_data;
};

int main()
{
    circular_array<int, 3> v = {1,2,3};

    std::cout << v[-3] << std::endl;
    std::cout << v[-2] << std::endl;
    std::cout << v[-1] << std::endl;
    std::cout << v[0] << std::endl;
    std::cout << v[1] << std::endl;
    std::cout << v[2] << std::endl;
    std::cout << v[3] << std::endl;


    return 0;
}

which outputs

1
2
3
1
2
3
1

which works all the way down to c++11 as far as I have tested. I still have to implement all the other forwarding calls but the principle is clear though kludgy.

https://godbolt.org/z/Kqhsa8cP5

Related