Implementation of static_vector using an array of std::aligned_storage, with std::launder and forwarding
I'm trying to expand on the implementation of static_vector on the std::aligned_storage reference page, but would like to split it into two parts. First, an aligned_storage_array that supports perfect forwarding (so that I can emplace into it) and doesn't require a default constructor, and then the actual static_vector infrastructure that builds upon it. This will let me use the aligned_storage_array for some other static data structures I plan to make in the future.
aligned_storage_array.h
#pragma once
#include <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace nonstd
{
template<class T, std::size_t N>
struct aligned_storage_array
{
public:
aligned_storage_array() = default;
~aligned_storage_array() = default;
aligned_storage_array(aligned_storage_array&& rhs)
//requires std::is_move_constructible_v<T>
= default;
aligned_storage_array& operator=(aligned_storage_array&& rhs)
//requires std::is_move_assignable_v<T>
= default;
aligned_storage_array(const aligned_storage_array& rhs)
//requires std::is_copy_constructible_v<T>
= default;
aligned_storage_array& operator=(const aligned_storage_array& rhs)
//requires std::is_copy_assignable_v<T>
= default;
// Size
constexpr std::size_t size() const noexcept { return N; }
constexpr std::size_t max_size() const noexcept { return N; }
// Access
inline T& operator(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data[pos])));
}
inline const T& operator(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos])));
}
inline T& at(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data.at(pos))));
}
inline const T& at(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos))));
}
// Operations
template<typename ...Args>
inline T& emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data[pos]))
T(std::forward<Args>(args)...);
}
template<typename ...Args>
inline T& bounded_emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data.at(pos)))
T(std::forward<Args>(args)...);
}
inline void destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos]))));
}
inline void bounded_destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos)))));
}
private:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> m_data;
};
}
static_vector.h
#pragma once
#include <array>
#include <stdexcept>
#include "aligned_storage_array.h"
namespace nonstd
{
template<class T, std::size_t N>
struct static_vector
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = value_type*;
using const_iterator = const value_type*;
using size_type = std::size_t;
static_vector() = default;
~static_vector() { destroy_n(m_size); }
static_vector(static_vector&& rhs) = default;
static_vector& operator=(static_vector&& rhs) = default;
static_vector(const static_vector& rhs) = default;
static_vector& operator=(const static_vector& rhs) = default;
// Size and capacity
constexpr std::size_t size() const { return m_size; }
constexpr std::size_t max_size() const { return N; }
constexpr bool empty() const { return m_size == 0; }
// Iterators
inline iterator begin() { return &m_data[0]; }
inline const_iterator begin() const { return &m_data[0]; }
inline iterator end() { return &m_data[m_size]; }
inline const_iterator end() const { return &m_data[m_size]; }
// Access
inline T& operator(std::size_t pos)
{
return m_data[pos];
}
inline const T& operator(std::size_t pos) const
{
return m_data[pos];
}
inline T& at(std::size_t pos)
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
inline const T& at(std::size_t pos) const
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
// Operations
template<typename ...Args>
inline T& emplace_back(Args&&... args)
{
T& result = m_data.bounded_emplace(m_size, args...);
++m_size;
return result;
}
inline void clear()
{
std::size_t count = m_size;
m_size = 0; // In case of exception
destroy_n(count);
}
private:
void destroy_n(std::size_t count)
{
for (std::size_t pos = 0; pos < count; ++pos)
m_data.destroy(pos);
}
aligned_storage_array<T, N> m_data;
std::size_t m_size = 0;
};
}
A full functional test setup is available here (wandbox). The concept lines can be uncommented and will parse in a compiler that supports them. Mostly I'd like some extra eyes to help determine:
- Is this actually safe for placement new with respect to alignment?
- Is the use of std::launder correct?
- Is the use of reinterpret_cast correct (or should it be two static_casts instead?)
- Are there any hidden pitfalls I should watch out for here (aside from the maximum capacity)?
- Am I being sufficiently paranoid (::new, std::address_of, std::destroy_at)? Any other safety features I can put in to handle risky operator overloads?
- How should I actually handle copy and move? The aligned_storage will happily copy irrespective of whether or not T actually has copy and move functions. I don't think these concepts are enough because I'm not calling the actual constructors or operators. However in the array base I don't know which entries are and aren't valid to copy/move.
I'm told this is similar to something like boost::small_vector, but I want the aligned_storage_array generalized because I want to use it for a number of different static structures later. Also I would like to learn more about alignment/placement new, forwarding, and launder.
Thank you!
c++ memory-management template c++17 sfinae
New contributor
add a comment |
I'm trying to expand on the implementation of static_vector on the std::aligned_storage reference page, but would like to split it into two parts. First, an aligned_storage_array that supports perfect forwarding (so that I can emplace into it) and doesn't require a default constructor, and then the actual static_vector infrastructure that builds upon it. This will let me use the aligned_storage_array for some other static data structures I plan to make in the future.
aligned_storage_array.h
#pragma once
#include <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace nonstd
{
template<class T, std::size_t N>
struct aligned_storage_array
{
public:
aligned_storage_array() = default;
~aligned_storage_array() = default;
aligned_storage_array(aligned_storage_array&& rhs)
//requires std::is_move_constructible_v<T>
= default;
aligned_storage_array& operator=(aligned_storage_array&& rhs)
//requires std::is_move_assignable_v<T>
= default;
aligned_storage_array(const aligned_storage_array& rhs)
//requires std::is_copy_constructible_v<T>
= default;
aligned_storage_array& operator=(const aligned_storage_array& rhs)
//requires std::is_copy_assignable_v<T>
= default;
// Size
constexpr std::size_t size() const noexcept { return N; }
constexpr std::size_t max_size() const noexcept { return N; }
// Access
inline T& operator(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data[pos])));
}
inline const T& operator(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos])));
}
inline T& at(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data.at(pos))));
}
inline const T& at(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos))));
}
// Operations
template<typename ...Args>
inline T& emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data[pos]))
T(std::forward<Args>(args)...);
}
template<typename ...Args>
inline T& bounded_emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data.at(pos)))
T(std::forward<Args>(args)...);
}
inline void destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos]))));
}
inline void bounded_destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos)))));
}
private:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> m_data;
};
}
static_vector.h
#pragma once
#include <array>
#include <stdexcept>
#include "aligned_storage_array.h"
namespace nonstd
{
template<class T, std::size_t N>
struct static_vector
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = value_type*;
using const_iterator = const value_type*;
using size_type = std::size_t;
static_vector() = default;
~static_vector() { destroy_n(m_size); }
static_vector(static_vector&& rhs) = default;
static_vector& operator=(static_vector&& rhs) = default;
static_vector(const static_vector& rhs) = default;
static_vector& operator=(const static_vector& rhs) = default;
// Size and capacity
constexpr std::size_t size() const { return m_size; }
constexpr std::size_t max_size() const { return N; }
constexpr bool empty() const { return m_size == 0; }
// Iterators
inline iterator begin() { return &m_data[0]; }
inline const_iterator begin() const { return &m_data[0]; }
inline iterator end() { return &m_data[m_size]; }
inline const_iterator end() const { return &m_data[m_size]; }
// Access
inline T& operator(std::size_t pos)
{
return m_data[pos];
}
inline const T& operator(std::size_t pos) const
{
return m_data[pos];
}
inline T& at(std::size_t pos)
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
inline const T& at(std::size_t pos) const
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
// Operations
template<typename ...Args>
inline T& emplace_back(Args&&... args)
{
T& result = m_data.bounded_emplace(m_size, args...);
++m_size;
return result;
}
inline void clear()
{
std::size_t count = m_size;
m_size = 0; // In case of exception
destroy_n(count);
}
private:
void destroy_n(std::size_t count)
{
for (std::size_t pos = 0; pos < count; ++pos)
m_data.destroy(pos);
}
aligned_storage_array<T, N> m_data;
std::size_t m_size = 0;
};
}
A full functional test setup is available here (wandbox). The concept lines can be uncommented and will parse in a compiler that supports them. Mostly I'd like some extra eyes to help determine:
- Is this actually safe for placement new with respect to alignment?
- Is the use of std::launder correct?
- Is the use of reinterpret_cast correct (or should it be two static_casts instead?)
- Are there any hidden pitfalls I should watch out for here (aside from the maximum capacity)?
- Am I being sufficiently paranoid (::new, std::address_of, std::destroy_at)? Any other safety features I can put in to handle risky operator overloads?
- How should I actually handle copy and move? The aligned_storage will happily copy irrespective of whether or not T actually has copy and move functions. I don't think these concepts are enough because I'm not calling the actual constructors or operators. However in the array base I don't know which entries are and aren't valid to copy/move.
I'm told this is similar to something like boost::small_vector, but I want the aligned_storage_array generalized because I want to use it for a number of different static structures later. Also I would like to learn more about alignment/placement new, forwarding, and launder.
Thank you!
c++ memory-management template c++17 sfinae
New contributor
add a comment |
I'm trying to expand on the implementation of static_vector on the std::aligned_storage reference page, but would like to split it into two parts. First, an aligned_storage_array that supports perfect forwarding (so that I can emplace into it) and doesn't require a default constructor, and then the actual static_vector infrastructure that builds upon it. This will let me use the aligned_storage_array for some other static data structures I plan to make in the future.
aligned_storage_array.h
#pragma once
#include <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace nonstd
{
template<class T, std::size_t N>
struct aligned_storage_array
{
public:
aligned_storage_array() = default;
~aligned_storage_array() = default;
aligned_storage_array(aligned_storage_array&& rhs)
//requires std::is_move_constructible_v<T>
= default;
aligned_storage_array& operator=(aligned_storage_array&& rhs)
//requires std::is_move_assignable_v<T>
= default;
aligned_storage_array(const aligned_storage_array& rhs)
//requires std::is_copy_constructible_v<T>
= default;
aligned_storage_array& operator=(const aligned_storage_array& rhs)
//requires std::is_copy_assignable_v<T>
= default;
// Size
constexpr std::size_t size() const noexcept { return N; }
constexpr std::size_t max_size() const noexcept { return N; }
// Access
inline T& operator(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data[pos])));
}
inline const T& operator(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos])));
}
inline T& at(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data.at(pos))));
}
inline const T& at(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos))));
}
// Operations
template<typename ...Args>
inline T& emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data[pos]))
T(std::forward<Args>(args)...);
}
template<typename ...Args>
inline T& bounded_emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data.at(pos)))
T(std::forward<Args>(args)...);
}
inline void destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos]))));
}
inline void bounded_destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos)))));
}
private:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> m_data;
};
}
static_vector.h
#pragma once
#include <array>
#include <stdexcept>
#include "aligned_storage_array.h"
namespace nonstd
{
template<class T, std::size_t N>
struct static_vector
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = value_type*;
using const_iterator = const value_type*;
using size_type = std::size_t;
static_vector() = default;
~static_vector() { destroy_n(m_size); }
static_vector(static_vector&& rhs) = default;
static_vector& operator=(static_vector&& rhs) = default;
static_vector(const static_vector& rhs) = default;
static_vector& operator=(const static_vector& rhs) = default;
// Size and capacity
constexpr std::size_t size() const { return m_size; }
constexpr std::size_t max_size() const { return N; }
constexpr bool empty() const { return m_size == 0; }
// Iterators
inline iterator begin() { return &m_data[0]; }
inline const_iterator begin() const { return &m_data[0]; }
inline iterator end() { return &m_data[m_size]; }
inline const_iterator end() const { return &m_data[m_size]; }
// Access
inline T& operator(std::size_t pos)
{
return m_data[pos];
}
inline const T& operator(std::size_t pos) const
{
return m_data[pos];
}
inline T& at(std::size_t pos)
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
inline const T& at(std::size_t pos) const
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
// Operations
template<typename ...Args>
inline T& emplace_back(Args&&... args)
{
T& result = m_data.bounded_emplace(m_size, args...);
++m_size;
return result;
}
inline void clear()
{
std::size_t count = m_size;
m_size = 0; // In case of exception
destroy_n(count);
}
private:
void destroy_n(std::size_t count)
{
for (std::size_t pos = 0; pos < count; ++pos)
m_data.destroy(pos);
}
aligned_storage_array<T, N> m_data;
std::size_t m_size = 0;
};
}
A full functional test setup is available here (wandbox). The concept lines can be uncommented and will parse in a compiler that supports them. Mostly I'd like some extra eyes to help determine:
- Is this actually safe for placement new with respect to alignment?
- Is the use of std::launder correct?
- Is the use of reinterpret_cast correct (or should it be two static_casts instead?)
- Are there any hidden pitfalls I should watch out for here (aside from the maximum capacity)?
- Am I being sufficiently paranoid (::new, std::address_of, std::destroy_at)? Any other safety features I can put in to handle risky operator overloads?
- How should I actually handle copy and move? The aligned_storage will happily copy irrespective of whether or not T actually has copy and move functions. I don't think these concepts are enough because I'm not calling the actual constructors or operators. However in the array base I don't know which entries are and aren't valid to copy/move.
I'm told this is similar to something like boost::small_vector, but I want the aligned_storage_array generalized because I want to use it for a number of different static structures later. Also I would like to learn more about alignment/placement new, forwarding, and launder.
Thank you!
c++ memory-management template c++17 sfinae
New contributor
I'm trying to expand on the implementation of static_vector on the std::aligned_storage reference page, but would like to split it into two parts. First, an aligned_storage_array that supports perfect forwarding (so that I can emplace into it) and doesn't require a default constructor, and then the actual static_vector infrastructure that builds upon it. This will let me use the aligned_storage_array for some other static data structures I plan to make in the future.
aligned_storage_array.h
#pragma once
#include <array>
#include <memory>
#include <stdexcept>
#include <type_traits>
namespace nonstd
{
template<class T, std::size_t N>
struct aligned_storage_array
{
public:
aligned_storage_array() = default;
~aligned_storage_array() = default;
aligned_storage_array(aligned_storage_array&& rhs)
//requires std::is_move_constructible_v<T>
= default;
aligned_storage_array& operator=(aligned_storage_array&& rhs)
//requires std::is_move_assignable_v<T>
= default;
aligned_storage_array(const aligned_storage_array& rhs)
//requires std::is_copy_constructible_v<T>
= default;
aligned_storage_array& operator=(const aligned_storage_array& rhs)
//requires std::is_copy_assignable_v<T>
= default;
// Size
constexpr std::size_t size() const noexcept { return N; }
constexpr std::size_t max_size() const noexcept { return N; }
// Access
inline T& operator(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data[pos])));
}
inline const T& operator(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos])));
}
inline T& at(std::size_t pos)
{
return *std::launder(
reinterpret_cast<T*>(
std::addressof(m_data.at(pos))));
}
inline const T& at(std::size_t pos) const
{
return *std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos))));
}
// Operations
template<typename ...Args>
inline T& emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data[pos]))
T(std::forward<Args>(args)...);
}
template<typename ...Args>
inline T& bounded_emplace(size_t pos, Args&&... args)
{
return
*::new(std::addressof(m_data.at(pos)))
T(std::forward<Args>(args)...);
}
inline void destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data[pos]))));
}
inline void bounded_destroy(std::size_t pos)
{
std::destroy_at(
std::launder(
reinterpret_cast<const T*>(
std::addressof(m_data.at(pos)))));
}
private:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> m_data;
};
}
static_vector.h
#pragma once
#include <array>
#include <stdexcept>
#include "aligned_storage_array.h"
namespace nonstd
{
template<class T, std::size_t N>
struct static_vector
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = value_type*;
using const_iterator = const value_type*;
using size_type = std::size_t;
static_vector() = default;
~static_vector() { destroy_n(m_size); }
static_vector(static_vector&& rhs) = default;
static_vector& operator=(static_vector&& rhs) = default;
static_vector(const static_vector& rhs) = default;
static_vector& operator=(const static_vector& rhs) = default;
// Size and capacity
constexpr std::size_t size() const { return m_size; }
constexpr std::size_t max_size() const { return N; }
constexpr bool empty() const { return m_size == 0; }
// Iterators
inline iterator begin() { return &m_data[0]; }
inline const_iterator begin() const { return &m_data[0]; }
inline iterator end() { return &m_data[m_size]; }
inline const_iterator end() const { return &m_data[m_size]; }
// Access
inline T& operator(std::size_t pos)
{
return m_data[pos];
}
inline const T& operator(std::size_t pos) const
{
return m_data[pos];
}
inline T& at(std::size_t pos)
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
inline const T& at(std::size_t pos) const
{
if (pos >= m_size)
throw std::out_of_range("static_vector subscript out of range");
return m_data.at(pos);
}
// Operations
template<typename ...Args>
inline T& emplace_back(Args&&... args)
{
T& result = m_data.bounded_emplace(m_size, args...);
++m_size;
return result;
}
inline void clear()
{
std::size_t count = m_size;
m_size = 0; // In case of exception
destroy_n(count);
}
private:
void destroy_n(std::size_t count)
{
for (std::size_t pos = 0; pos < count; ++pos)
m_data.destroy(pos);
}
aligned_storage_array<T, N> m_data;
std::size_t m_size = 0;
};
}
A full functional test setup is available here (wandbox). The concept lines can be uncommented and will parse in a compiler that supports them. Mostly I'd like some extra eyes to help determine:
- Is this actually safe for placement new with respect to alignment?
- Is the use of std::launder correct?
- Is the use of reinterpret_cast correct (or should it be two static_casts instead?)
- Are there any hidden pitfalls I should watch out for here (aside from the maximum capacity)?
- Am I being sufficiently paranoid (::new, std::address_of, std::destroy_at)? Any other safety features I can put in to handle risky operator overloads?
- How should I actually handle copy and move? The aligned_storage will happily copy irrespective of whether or not T actually has copy and move functions. I don't think these concepts are enough because I'm not calling the actual constructors or operators. However in the array base I don't know which entries are and aren't valid to copy/move.
I'm told this is similar to something like boost::small_vector, but I want the aligned_storage_array generalized because I want to use it for a number of different static structures later. Also I would like to learn more about alignment/placement new, forwarding, and launder.
Thank you!
c++ memory-management template c++17 sfinae
c++ memory-management template c++17 sfinae
New contributor
New contributor
New contributor
asked 5 mins ago
rtekrtek
6
6
New contributor
New contributor
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
rtek is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211362%2fimplementation-of-static-vector-using-an-array-of-stdaligned-storage-with-std%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
rtek is a new contributor. Be nice, and check out our Code of Conduct.
rtek is a new contributor. Be nice, and check out our Code of Conduct.
rtek is a new contributor. Be nice, and check out our Code of Conduct.
rtek is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211362%2fimplementation-of-static-vector-using-an-array-of-stdaligned-storage-with-std%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown