Last active
January 23, 2020 08:18
-
-
Save trueqbit/820392a869d071aaf811a320cb02267f to your computer and use it in GitHub Desktop.
C++17: Experiment with a C array wrapper container that allows construction/assignment from a C array and from arrays differing in type
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <type_traits> | |
#include <cstddef> | |
#include <memory> | |
#include <utility> | |
#include <algorithm> | |
using namespace std; | |
enum class array2_value_init_tag {}; | |
enum class array2_default_init_tag {}; | |
inline constexpr array2_value_init_tag array2_value_init{}; | |
inline constexpr array2_default_init_tag array2_default_init{}; | |
template<typename T, size_t N> | |
struct caggregate | |
{ | |
using carray_t = T[N]; | |
using carray_ct = const T[N]; | |
carray_t elems_; | |
}; | |
/** Encapsulates a C array as an aggregate, but defers its initialization. | |
* Defers initialization of the underlying aggregate (by means of a union). | |
This allows derived classes to provide user-defined constructors. | |
* Direct aggregate initialization is mimicked by a variadic constructor. | |
Note: The default is to value-initialize instead of to default-initialize. | |
The reason for this is: In order to make direct-initialization possible, | |
the proxy has to provide a variadic constructor, following a user-defined | |
default-constructor, which in turn makes it impossible to differentiate between | |
no-init default initialization and explicit value-initialization. Default-initialization can be requested explicitly | |
(which can be seen as a security enhancement). | |
Distinct specializations for different properties of the underlying element's type are available. | |
The technical implementation uses 2 layers of indirection: | |
1) Defer+Control automatic aggregate handling by means of a union. | |
2) Benefit from automatic aggregate handling by placing a named aggregate into the union. | |
3) Manually provide constructors/destructors/copy+move operators. | |
*/ | |
template< | |
typename T, size_t N, | |
bool HasTrivialDefaultInit = is_trivially_default_constructible_v<T>, | |
bool HasTrivialDtor = is_trivially_destructible_v<T> | |
> | |
struct array2_proxy; | |
// no specialization for: | |
// - HasTrivialDefaultInit = true | |
// - HasTrivialDtor = false | |
template<typename T, size_t N> | |
struct array2_proxy<T, N, true, false>; | |
// partial specialization for: | |
// - HasTrivialDefaultInit = true | |
// - HasTrivialDtor = true/false | |
// means: | |
// - trivial default initialization (which allows the aggregate to remain uninitialized) | |
// - default dtor | |
template<typename T, size_t N, bool HasTrivialDtor> | |
struct array2_proxy<T, N, true, HasTrivialDtor> | |
{ | |
using aggregate = caggregate<T, N>; | |
~array2_proxy() = default; | |
// emulate default initialization | |
// (can never be constexpr) | |
array2_proxy(array2_default_init_tag) | |
{} | |
// emulate value initialization | |
// (default ctor) | |
constexpr array2_proxy(array2_value_init_tag = {}) : | |
a{} | |
{} | |
// emulate direct initialization | |
template< | |
typename First, typename... Rest, | |
enable_if_t<is_constructible_v<T, First&&>, int> = 0 | |
> | |
constexpr array2_proxy(First&& first, Rest&&... rest) : | |
a{ forward<First>(first), forward<Rest>(rest)... } | |
{} | |
// upstream direct initialization | |
constexpr array2_proxy(aggregate&& a) : | |
a{ move(a) } | |
{} | |
constexpr array2_proxy(const array2_proxy& other) = default; | |
constexpr array2_proxy(array2_proxy&& other) = default; | |
constexpr array2_proxy& operator =(const array2_proxy& other) = default; | |
constexpr array2_proxy& operator =(array2_proxy&& other) = default; | |
aggregate a; | |
protected: | |
// no-init | |
// (can never be constexpr) | |
array2_proxy(std::nullptr_t) | |
{} | |
}; | |
// specialization for: | |
// - HasTrivialDefaultInit = false | |
// - HasTrivialDtor = false | |
// means: | |
// - non-trivial default initialization | |
// - non-trivial dtor | |
template<typename T, size_t N> | |
struct array2_proxy<T, N, false, false> | |
{ | |
using aggregate = caggregate<T, N>; | |
~array2_proxy() | |
{ | |
a.~aggregate(); | |
} | |
// emulate default initialization | |
array2_proxy(array2_default_init_tag): | |
a{} | |
{} | |
// emulate value initialization | |
// (default ctor) | |
array2_proxy(array2_value_init_tag = {}): | |
a{} | |
{} | |
// emulate direct initialization | |
template< | |
typename First, typename... Rest, | |
enable_if_t<is_constructible_v<T, First&&>, int> = 0 | |
> | |
array2_proxy(First&& first, Rest&&... rest) : | |
a{ std::forward<First>(first), std::forward<Rest>(rest)... } | |
{} | |
// upstream direct initialization | |
array2_proxy(aggregate&& a) : | |
a{ move(a) } | |
{} | |
array2_proxy(const array2_proxy& other) : | |
a{ other.a } | |
{} | |
array2_proxy(array2_proxy&& other) : | |
a{ move(other.a) } | |
{} | |
array2_proxy& operator =(const array2_proxy& other) | |
{ | |
a = other.a; | |
return *this; | |
} | |
array2_proxy& operator =(array2_proxy&& other) | |
{ | |
a = move(other.a); | |
return *this; | |
} | |
// prevent automatic code generation | |
union | |
{ | |
// benefit from generated aggregate operations | |
aggregate a; | |
}; | |
protected: | |
// no-init | |
// (can never be constexpr) | |
array2_proxy(std::nullptr_t) | |
{} | |
}; | |
// specialization for: | |
// - bool HasTrivialDefaultInit = false | |
// - bool HasTrivialDtor = true | |
// means: | |
// - non-trivial default initialization | |
// - trivial dtor | |
template<typename T, size_t N> | |
struct array2_proxy<T, N, false, true> | |
{ | |
using aggregate = caggregate<T, N>; | |
~array2_proxy() = default; | |
// emulate default initialization | |
constexpr array2_proxy(array2_default_init_tag): | |
a{} | |
{} | |
// emulate value initialization | |
// (default ctor) | |
constexpr array2_proxy(array2_value_init_tag = {}): | |
a{} | |
{} | |
// emulate direct initialization | |
template< | |
typename First, typename... Rest, | |
enable_if_t<is_constructible_v<T, First&&>, int> = 0 | |
> | |
constexpr array2_proxy(First&& first, Rest&&... rest) : | |
a{ std::forward<First>(first), std::forward<Rest>(rest)... } | |
{} | |
// upstream direct initialization | |
constexpr array2_proxy(aggregate&& a) : | |
a{ move(a) } | |
{} | |
constexpr array2_proxy(const array2_proxy& other) : | |
a{ other.a } | |
{} | |
constexpr array2_proxy(array2_proxy&& other) : | |
a{ move(other.a) } | |
{} | |
constexpr array2_proxy& operator =(const array2_proxy& other) | |
{ | |
a = other.a; | |
return *this; | |
} | |
constexpr array2_proxy& operator =(array2_proxy&& other) | |
{ | |
a = move(other.a); | |
return *this; | |
} | |
// prevent automatic code generation | |
union | |
{ | |
// benefit from generated aggregate operations | |
aggregate a; | |
}; | |
protected: | |
// no-init | |
// (can never be constexpr) | |
array2_proxy(std::nullptr_t) | |
{} | |
}; | |
template<typename T, size_t N> | |
class array2; | |
template<typename T, size_t N> | |
class array2: public array2_proxy<T, N> | |
{ | |
using base_t = array2_proxy<T, N>; | |
public: | |
using aggregate = typename base_t::aggregate; | |
using carray_t = typename aggregate::carray_t; | |
using carray_ct = typename aggregate::carray_ct; | |
using value_type = T; | |
using size_type = size_t; | |
using difference_type = ptrdiff_t; | |
using pointer = T*; | |
using const_pointer = const T*; | |
using reference = T&; | |
using const_reference = const T&; | |
using base_t::base_t; | |
array2(const array2&) = default; | |
array2(array2&&) = default; | |
array2& operator =(const array2&) = default; | |
array2& operator =(array2&&) = default; | |
// copy-construct from same array | |
array2(carray_ct right) : | |
base_t{ nullptr } | |
{ | |
uninitialized_copy_n(right, N, this->a.elems_); | |
} | |
// move-construct from same array | |
array2(carray_t&& right) : | |
base_t{ nullptr } | |
{ | |
uninitialized_move_n(right, N, this->a.elems_); | |
} | |
// copy-assign from same array | |
array2& operator =(carray_ct right) | |
{ | |
copy_n(right, N, this->a.elems_); | |
return *this; | |
} | |
// move-assign from same array | |
array2& operator =(carray_t&& right) | |
{ | |
move(right, right + N, this->a.elems_); | |
return *this; | |
} | |
// copy-construct from any array of same size | |
template< | |
typename U, | |
enable_if_t<is_constructible_v<T, const U&>, int> = 0 | |
> | |
array2(const array2<U, N>& other): | |
base_t{ nullptr } | |
{ | |
uninitialized_copy_n(other.a.elems_, N, this->a.elems_); | |
} | |
// move-construct from any array of same size | |
template< | |
typename U, | |
enable_if_t<is_constructible_v<T, U&&>, int> = 0 | |
> | |
array2(array2<U, N>&& other) : | |
base_t{ nullptr } | |
{ | |
uninitialized_move_n(other.a.elems_, N, this->a.elems_); | |
} | |
// copy-assign from any array of same size | |
template< | |
typename U, | |
enable_if_t<is_convertible_v<T, const U&>, int> = 0 | |
> | |
array2& operator =(const array2& other) | |
{ | |
if (addressof(other) != this) | |
copy_n(other.a.elems_, N, this->a.elems_); | |
return *this; | |
} | |
// move-assign from any array of same size | |
template< | |
typename U, | |
enable_if_t<is_convertible_v<T, U&&>, int> = 0 | |
> | |
array2& operator =(array2&& other) | |
{ | |
if (addressof(other) != this) | |
move(other.a.elems_, other.a.elems_ + N, this->a.elems_); | |
return *this; | |
} | |
// copy-construct from any C array | |
template< | |
typename U, size_t M, | |
enable_if_t<is_constructible_v<T, U&>, int> = 0 | |
> | |
explicit array2(U (&right)[M]) : | |
base_t{ nullptr } | |
{ | |
static_assert(M <= N); | |
uninitialized_copy_n(right, min<>(M, N), this->a.elems_); | |
// value-construct remaining elements N-M | |
if constexpr (M < N) | |
uninitialized_value_construct_n(this->a.elems_ + M, N - M); | |
} | |
// copy-construct from any C array | |
template< | |
typename U, size_t M, | |
enable_if_t<is_constructible_v<T, U&&>, int> = 0 | |
> | |
explicit array2(U (&&right)[M]) : | |
base_t{ nullptr } | |
{ | |
static_assert(M <= N); | |
uninitialized_move_n(right, min<>(M, N), this->a.elems_); | |
// value-construct remaining elements N-M | |
if constexpr (M < N) | |
uninitialized_value_construct_n(this->a.elems_ + M, N - M); | |
} | |
// copy-assign from any C array of same size | |
template< | |
typename U, | |
enable_if_t<is_assignable_v<T&, U&>, int> = 0 | |
> | |
array2& operator =(U (&right)[N]) | |
{ | |
copy_n(right, N, this->a.elems_); | |
return *this; | |
} | |
// copy-assign from any C array of same size | |
template< | |
typename U, | |
enable_if_t<is_assignable_v<T&, U&&>, int> = 0 | |
> | |
array2& operator =(U (&&right)[N]) | |
{ | |
move(right, right + N, this->a.elems_); | |
return *this; | |
} | |
[[nodiscard]] constexpr size_type size() const noexcept | |
{ | |
return N; | |
} | |
[[nodiscard]] constexpr size_type max_size() const noexcept | |
{ | |
return N; | |
} | |
[[nodiscard]] constexpr bool empty() const noexcept | |
{ | |
return false; | |
} | |
[[nodiscard]] constexpr reference operator [](size_t i) noexcept | |
{ | |
return this->a.elems_[i]; | |
} | |
[[nodiscard]] constexpr const_reference operator [](size_t i) const noexcept | |
{ | |
return this->a.elems_[i]; | |
} | |
[[nodiscard]] constexpr reference front() noexcept | |
{ | |
return this->a.elems_[0]; | |
} | |
[[nodiscard]] constexpr const_reference front() const noexcept | |
{ | |
return this->a.elems_[0]; | |
} | |
[[nodiscard]] constexpr reference back() noexcept | |
{ | |
return this->a.elems_[N - 1]; | |
} | |
[[nodiscard]] constexpr const_reference back() const noexcept | |
{ | |
return this->a.elems_[N - 1]; | |
} | |
[[nodiscard]] constexpr T* data() noexcept | |
{ | |
return this->a.elems_; | |
} | |
[[nodiscard]] constexpr const T* data() const noexcept | |
{ | |
return this->a.elems_; | |
} | |
}; | |
// class template parameter deduction guides | |
template <typename First, typename... Rest> | |
caggregate(First&&, Rest&&...)->caggregate<First, 1u + sizeof...(Rest)>; | |
template <typename First, typename... Rest> | |
array2(First&&, Rest&&...)->array2<remove_cv_t<remove_reference_t<First>>, 1u + sizeof...(Rest)>; | |
template <typename T, size_t N> | |
array2(caggregate<T, N>&&)->array2<T, N>; | |
template <typename T, size_t N> | |
array2(T(&)[N])->array2<T, N>; | |
template <typename T, size_t N> | |
array2(const T(&)[N])->array2<T, N>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
#include <iostream> | |
#include <ios> | |
#include "array2.h" | |
using namespace std::string_literals; | |
int main() | |
{ | |
struct S | |
{ | |
constexpr S() = default; | |
constexpr S(int i) : x{ i } {} | |
int x = 4; | |
}; | |
cout << "HasTrivialDefaultInit<int>: " << boolalpha << (is_trivially_default_constructible_v<int>) << "\n"; | |
cout << "HasTrivialDtor<int>: " << boolalpha << (is_trivially_destructible_v<int>) << "\n"; | |
cout << "HasTrivialDefaultInit<string>: " << boolalpha << (is_trivially_default_constructible_v<string>) << "\n"; | |
cout << "HasTrivialDtor<string>: " << boolalpha << (is_trivially_destructible_v<string>) << "\n"; | |
cout << "HasTrivialDefaultInit<S>: " << boolalpha << (is_trivially_default_constructible_v<S>) << "\n"; | |
cout << "HasTrivialDtor<S>: " << boolalpha << (is_trivially_destructible_v<S>) << "\n"; | |
{ | |
int acarray3[3] = { 1, 2, 3 }; | |
int acarray2[] = { 4, 5 }; | |
array2<int, 3> a0; | |
array2 a1{ 1, 2, 3 }; | |
array2<int, 3> a2 = { 1, 2, 3 }; | |
array2<int, 3> a3{ acarray3 }; | |
a3 = acarray3; | |
array2<int, 3> a5{ acarray2 }; | |
array2 ca{ caggregate{1} }; | |
array2<long, 3> l0{ a0 }; | |
l0 = a0; | |
array2<long, 3> l1{ move(acarray3) }; | |
l1 = move(acarray3); | |
// impossible | |
//constexpr array2<int, 1> defaultconstexprint(array2_default_init); | |
constexpr array2_proxy<int, 1> oneinttest{ /*array2_value_init*/ }; | |
constexpr array2<int, 1> oneint{ /*array2_value_init*/ }; | |
// gcc/clang choke on this? msvc works... | |
//constexpr const int* i = oneint.data(); | |
//constexpr const int& x = oneint[0]; | |
constexpr array2<int, 3> threeints{ 1, 2 }; | |
array2 deduced1{ 1, 2, 3 }; | |
array2 deduced2{ oneint }; | |
array2 deduced3 = { 1, 2, 3 }; | |
array2 deduced4 = oneint; | |
array2 deduced5{ acarray3 }; | |
array2 deduced6 = acarray3; | |
} | |
{ | |
S acarray3[3] = { 1, 2, 3 }; | |
S acarray2[] = { 4, 5 }; | |
array2<S, 3> a0; | |
array2 a1{ S(1), S(2), S(3) }; | |
array2<S, 3> a2 = { 1, 2, 3 }; | |
array2<S, 3> a3{ acarray3 }; | |
a3 = acarray3; | |
array2<S, 3> a5{ acarray2 }; | |
array2 ca{ caggregate{S{}} }; | |
// impossible | |
//constexpr array2<S, 1> defaultconstexprint(array2_default_init); | |
constexpr array2<S, 1> one; | |
//constexpr const S* i = one.data(); | |
//constexpr const S& x = one[0]; | |
constexpr array2<S, 3> three{ 1, 2 }; | |
array2 deduced1{ 1, 2, 3 }; | |
array2 deduced2{ one }; | |
array2 deduced3 = { 1, 2, 3 }; | |
array2 deduced4 = one; | |
array2 deduced5{ acarray3 }; | |
array2 deduced6 = acarray3; | |
} | |
{ | |
string acarray3[3] = { "1", "2", "3" }; | |
string acarray2[] = { "4", "5" }; | |
array2<string, 3> a0; | |
array2<string, 3> a1{ "1", "2", "3" }; | |
array2<string, 3> a2 = { "1", "2", "3" }; | |
array2<string, 3> a3{ acarray3 }; | |
a3 = acarray3; | |
array2<string, 3> a5{ acarray2 }; | |
array2 ca{ caggregate{""s} }; | |
array2<const char*, 3> l00{ "1", "2", "3" }; | |
array2<string, 3> l0{ l00 }; | |
l0 = l00; | |
// impossible | |
//constexpr array2<string, 1> oneint(array2_value_init); | |
//constexpr const string* i = oneint.data(); | |
array2<string, 1> one; | |
array2 deduced1{ "1"s, "2"s, "3"s }; | |
array2 deduced2{ one }; | |
array2 deduced3 = { "1"s, "2"s, "3"s }; | |
array2 deduced4 = one; | |
array2 deduced5{ acarray3 }; | |
array2 deduced6 = acarray3; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment