b***@gmail.com

2017-01-12 21:39:59 UTC

Sometimes, there's a need a fixed-capacity vector. It's got some storage, a

size, and begin/end:

template <class T, size_t Capacity>

class fixed_vector {

using storage_t = std::aligned_storage_t<sizeof(T), alignof(T)>;

size_t length_;

storage_t data_[Capacity];

public:

// ...

T* begin() { return reinterpret_cast<T*>(&data_[0]); }

T* end() { return begin() + length_; } // <== (a)

T* end() { return reinterpret_cast<T*>(&data_[length]); } // <== (b)

// ...

};

The problem I immediately run into is:

(a) There's no T array at &data_[0], so the pointer arithmetic is UB (if

length_ > 1)

(b) This is probably UB in itself (no T at &data_[length_]), but then

additionally any algorithm I pass begin() and end() into will incur UB by

doing its own pointer arithmetic when it traverses that range.

One approach might then to be to implement a real iterator that is a

storage_t* but dereferences to a T. This is an annoying indirection, but

solves the begin()/end() problems, and hopefully the compiler can figure

out what I really mean. But then, what if I want to expose data()?

T* data() { return reinterpret_cast<T*>(&data_[0]); }

Wouldn't just about any use of data() be UB, regardless of what happens?

This isn't merely intellectual. I ask because gcc optimized away a bunch of

code that I actually need to run, and I'm not sure that it was a gcc bug to

have done so.

size, and begin/end:

template <class T, size_t Capacity>

class fixed_vector {

using storage_t = std::aligned_storage_t<sizeof(T), alignof(T)>;

size_t length_;

storage_t data_[Capacity];

public:

// ...

T* begin() { return reinterpret_cast<T*>(&data_[0]); }

T* end() { return begin() + length_; } // <== (a)

T* end() { return reinterpret_cast<T*>(&data_[length]); } // <== (b)

// ...

};

The problem I immediately run into is:

(a) There's no T array at &data_[0], so the pointer arithmetic is UB (if

length_ > 1)

(b) This is probably UB in itself (no T at &data_[length_]), but then

additionally any algorithm I pass begin() and end() into will incur UB by

doing its own pointer arithmetic when it traverses that range.

One approach might then to be to implement a real iterator that is a

storage_t* but dereferences to a T. This is an annoying indirection, but

solves the begin()/end() problems, and hopefully the compiler can figure

out what I really mean. But then, what if I want to expose data()?

T* data() { return reinterpret_cast<T*>(&data_[0]); }

Wouldn't just about any use of data() be UB, regardless of what happens?

This isn't merely intellectual. I ask because gcc optimized away a bunch of

code that I actually need to run, and I'm not sure that it was a gcc bug to

have done so.

--

---

You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.

To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.

To post to this group, send email to std-***@isocpp.org.

Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

---

You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.

To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.

To post to this group, send email to std-***@isocpp.org.

Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.