Afshin
Afshin

Reputation: 9173

Is this C++ ref example buggy?

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

Answers (1)

HolyBlackCat
HolyBlackCat

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 example unsigned char not type T. so when you are using &a[i] it access members assuming each member size is 1 (pointer was unsigned char) not sizeof(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

Related Questions