9

I would like to iterate through a pre-allocated float array with a custom container that does not owns the data, but acts on a segment of it. Example, naming the container class LinhaSobre:

std::unique_ptr<float[]> data(new float[720]);
...
//creates container to iterate 26 floats starting from from data[12]
    LinhaSobre cont(data.get()+12, 26); 
//sets those elements to 1.5
    for(size_t i = 0; i < cont.size(); i++)
        cont[i] = 1.5f;

Here's a possible implementation of the operator[] :

//...
//LinhaSobre has a member mem0 which is initialized
//as a pointer to where the interval starts
float & LinhaSobre::operator[] (size_t i)
{
    return *(mem0+i);
}

Notice that I'm returning a reference from LinhaSobre::operator[] to data that it does not owns. It should not interfere with the data's lifetime (constructors, destructors).

Now I want to expose the stored data by another pattern, std::array<float,4>, and not pure float. Example, naming the new class LinhaSobre4f:

std::unique_ptr<float[]> data(new float[720]);
...
//creates container to iterate 4 array<float, 4> starting from from data[12]
    LinhaSobre4f l(data.get()+(3*4), 4);
//sets those elements to {1.5f, 2.5f, 3.5f, 4.5f};
    for(size_t i = 0; i < l.size(); i++)
        l[i] = { {1.5f, 2.5f, 3.5f, 4.5f} };

Notice that I treat the items as an array. This would lead to some changes in the container class, my main concern is with the operator[], here's the full class code:

struct LinhaSobre4f
{
    LinhaSobre4f(float * pos_begin, size_t size_):
        pos0(pos_begin),
        size_(size_){}
    std::array<float, 4> & operator[](size_t i)const
    {
        std::array<float,4> * r = 
            reinterpret_cast<std::array<float,4>*> (pos0+(4*i));
        return *r;
    }
    size_t size()const
    {
        return size_;
    }
private:
    float * pos0;
    size_t size_;
};

The operator[] returns a reference to a block of memory treated as an std::array<float,4> that never really existed as such, but given the std::array memory layout guaranties, it works. I'm dubious about this, is it OK? (aside from memory alignment, which I'll guarantee). Am I allowed to expose an object like this, semantically? What is the correct term for this? (I've used fake object in the title).

Here's a live demo of the example. Here's another (the other link sometimes fails)

Kahler
  • 1,130
  • 6
  • 24
  • One thing which definitely stands out to me (and the compiler will warn you about this) is that in `operator[]` of `LinhaSobre4f` you are returning a reference to a temporary variable - you should change the signature of this method to return by value instead of reference. – sjrowlinson Apr 07 '16 at 23:42
  • 1
    (Am I?) The local (temporary) variable is a pointer, and I'm returning the dereference of it, not a reference to it. – Kahler Apr 07 '16 at 23:45
  • 1
    I *think* you're safe, though I have nothing to back it up. – kmdreko Apr 07 '16 at 23:45
  • *"Example, naming the container class `LinhaSobre`"* => Wouldn't `Container` be less distracting? – HostileFork says dont trust SE Apr 07 '16 at 23:50
  • @Kahler actually I might be wrong there, it should be safe with pointers. – sjrowlinson Apr 07 '16 at 23:53

3 Answers3

5

The C++ standard (I'm reading C++11) defines a std::array as follows:

The conditions for an aggregate (8.5.1) shall be met.

You are not guaranteed that a std::array is a POD. The C++ standard guarantees only that it's a class aggregate.

Based on that, I believe that your usage of reinterpret_cast to convert a POD array of floats to a std::array is undefined behavior.

Chances are that it'll work, with your compiler, but you are not guaranteed that this will be portable, or legal.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Searching about it, I've encountered the [std::is_pod](http://en.cppreference.com/w/cpp/types/is_pod), can I assume it's safe if [it returns true to std::array](http://ideone.com/ez6sX4)? – Kahler Apr 08 '16 at 21:40
  • 1
    It is safe if it returns true with your current compiler. However, keep in mind that if it returns true with your current compiler, there are no guarantees that it will return true with any other compiler, or any future or previous version of your compiler. – Sam Varshavchik Apr 09 '16 at 00:47
2

You might create a plain old reference_type:

struct LinhaSobre4f {
    struct Ref {
        Ref(float *m): m(m){};
        Ref &operator=(std::initializer_list<float> const &l) {
            std::copy(l.begin(), l.end(), m);
            return *this;
        }
    private:
        float *m;
    };
    Ref operator[](size_t i) { return m + 4 * i; }
private:
    float *m;
};
Kahler
  • 1,130
  • 6
  • 24
bipll
  • 11,747
  • 1
  • 18
  • 32
  • I understand the delegation, but the `operator[]` now returns a value, not a reference... My desire was to be able to operate directly in the data, apart that `Ref` now has to implement every desired operation. – Kahler Apr 08 '16 at 21:43
1

Adding on Sam Varshavchik's answer, you may be interested in the span type (formerly known as array_view).

The span type is an abstraction that provides a view over a contiguous sequence of objects, the storage of which is owned by some other object (more details in P0122R1, CppCoreGuidelines and Guidelines Support Library Review: span<T>).

Conceptually, a span is simply a pointer to some storage and a count of the elements accessible via that pointer. It's so small that it can be passed by value.

An open source (header only), reference implementation is available at https://github.com/Microsoft/GSL (the implementation generally assumes a platform that implements C++14 support. There are specific workarounds to support MSVC 2013 and 2015).

Community
  • 1
  • 1
manlio
  • 18,345
  • 14
  • 76
  • 126