Reputation: 9173
I was checking aligned_storage
in cppref, but I think its example is buggy. Here is the code:
#include <iostream>
#include <type_traits>
#include <string>
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};
int main()
{
static_vector<std::string, 10> v1;
v1.emplace_back(5, '*');
v1.emplace_back(10, '*');
std::cout << v1[0] << '\n' << v1[1] << '\n';
}
Here we want to create a static vector that uses placement new. The problem is that typename std::aligned_storage<sizeof(T), alignof(T)>::type
type is POD, not T
. so we need to cast it before using. I think code should be something like this:
#include <iostream>
#include <type_traits>
#include <string>
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
T* data_ptr = reinterpret_cast<T*>(data);
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data_ptr[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data_ptr[pos]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data_ptr[pos])->~T();
}
}
};
int main()
{
static_vector<std::string, 10> v1;
v1.emplace_back(5, '*');
v1.emplace_back(10, '*');
std::cout << v1[0] << '\n' << v1[1] << '\n';
}
Am I right? Although I don't know why original code works in clang.
Update:
Let me be more specific. By standard, aligned_storage
type is POD not T
. so its implementation can be as following:
template<std::size_t Len, std::size_t Align /* default alignment not implemented */>
struct aligned_storage {
struct type {
alignas(Align) unsigned char data[Len];
};
};
Now if you access this with data[pos]
, address will be increased based on unsigned char
size=1, not sizeof(T)
isn't it?
Upvotes: 2
Views: 109
Reputation: 96579
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
You seem to think that &data[i]
will return the address of i-th byte in this array, while in reality it will return the address of i * sizeof(std::aligned_storage<sizeof(T), alignof(T)>)
th byte, which is the same as i * sizeof(T)
th byte. Example.
OP: The problem is that in
aligned_storage
data is allocated in for exampleunsigned char
not typeT
. so when you are using&a[i]
it access members assuming each member size is1
(pointer wasunsigned char
) notsizeof(T)
.
This is not how it works. It doesn't matter what type aligned_storage
uses under the hood, unsigned char[N]
or something else.
For brevity, let's use using A = std::aligned_storage<sizeof(T), alignof(T)>::type;
.
The array data
has type A[N]
. When you apply operator []
to it, it decays to a pointer of type A *
.
data[i]
is equivalent to *(data + i)
. When adding an integer to a pointer, the integer is multiplied by sizeof
of the pointed type. The pointed type is A
, so sizeof(A)
is used (which is equal to sizeof(T)
).
Upvotes: 6