Skip to content

Instantly share code, notes, and snippets.

@talybin
Last active September 24, 2021 07:40
Show Gist options
  • Save talybin/17cb255796d6a7a628a93b34fedc591e to your computer and use it in GitHub Desktop.
Save talybin/17cb255796d6a7a628a93b34fedc591e to your computer and use it in GitHub Desktop.
A tuple that can be aligned to any number (not just power of 2) and does not use "pragma pack"
#pragma once
#include <type_traits>
#include <tuple>
#include <memory>
template <std::size_t N, class... Types>
struct packed_tuple;
// is_packed_tuple
template <class T>
struct is_packed_tuple : std::false_type { };
template <std::size_t N, class... Types>
struct is_packed_tuple<packed_tuple<N, Types...>> : std::true_type { };
template <class T>
inline constexpr bool is_packed_tuple_v = is_packed_tuple<T>::value;
// Concepts
template <class T, class = std::enable_if_t<is_packed_tuple_v<std::decay_t<T>>>>
using packed_tuple_t = T;
// Details
namespace detail
{
// Returns the first appearence of T in packed_tuple
// or sizeof...(Types) if T is not found
template <class T, class L>
struct index_of : std::integral_constant<std::size_t, 0> { };
template <class T, class L>
inline constexpr std::size_t index_of_v = index_of<T, L>::value;
template <class T, std::size_t N, class First, class... Rest>
struct index_of<T, packed_tuple<N, First, Rest...>> : std::integral_constant<
std::size_t,
std::is_same_v<T, First> ? 0 : index_of_v<T, packed_tuple<N, Rest...>> + 1> { };
} // namespace detail
// Get value by index argument
template <class Tuple>
inline constexpr decltype(auto)
get(std::in_place_index_t<0>, packed_tuple_t<Tuple>&& tup) noexcept
{ return std::forward<Tuple>(tup).get(); }
template <std::size_t I, class Tuple>
inline constexpr decltype(auto)
get(std::in_place_index_t<I>, packed_tuple_t<Tuple>&& tup) noexcept
{ return get(std::in_place_index<I - 1>, std::forward<Tuple>(tup).rest_); }
// Get value by type argument
template <class T, class Tuple>
inline constexpr decltype(auto)
get(std::in_place_type_t<T>, packed_tuple_t<Tuple>&& tup) noexcept
{
using I = detail::index_of<T, std::decay_t<Tuple>>;
return get(std::in_place_index<I::value>, std::forward<Tuple>(tup));
}
// Get value by index template
template <std::size_t I, class Tuple>
inline constexpr decltype(auto)
get(packed_tuple_t<Tuple>&& tup) noexcept
{ return get(std::in_place_index<I>, std::forward<Tuple>(tup)); }
// Get value by type template
template <class T, class Tuple>
inline constexpr decltype(auto)
get(packed_tuple_t<Tuple>&& tup) noexcept
{ return get(std::in_place_type<T>, std::forward<Tuple>(tup)); }
// This one does not used in specializations below but
// can be used as an empty tuple
template <std::size_t N, class... Types>
struct packed_tuple { };
// packed_tuple
template <std::size_t N, class First, class... Rest>
struct packed_tuple<N, First, Rest...> : packed_tuple<N, First> {
private:
packed_tuple<N, Rest...> rest_;
template <std::size_t I, class Tuple>
friend constexpr decltype(auto)
get(std::in_place_index_t<I>, Tuple&&) noexcept;
public:
// Constructors
constexpr packed_tuple() = default;
template <class Arg1, class... ArgN,
class = std::enable_if_t<std::is_constructible_v<First, Arg1>>
>
constexpr packed_tuple(Arg1&& arg1, ArgN&&... argn)
noexcept (
std::is_nothrow_constructible_v<packed_tuple<N, First>, Arg1> and
std::is_nothrow_constructible_v<packed_tuple<N, Rest...>, ArgN...>
)
: packed_tuple<N, First>(std::forward<Arg1>(arg1))
, rest_(std::forward<ArgN>(argn)...)
{ }
};
template <std::size_t N, class T>
struct packed_tuple<N, T> {
private:
// Aligned storage
unsigned char storage_[(sizeof(T) + N - 1) / N * N];
public:
// Default constructor
template <class... Args,
class = std::enable_if_t<std::is_constructible_v<T, Args...>>
>
constexpr packed_tuple(Args&&... args)
noexcept (std::is_nothrow_constructible_v<T, Args...>)
{ ::new (storage_) T(std::forward<Args>(args)...); }
// Copy constructor
constexpr packed_tuple(const packed_tuple& rhs)
noexcept (std::is_nothrow_copy_constructible_v<T>)
{ ::new (storage_) T(rhs.get()); }
// Move constructor
constexpr packed_tuple(packed_tuple&& rhs)
noexcept (std::is_nothrow_move_constructible_v<T>)
{ ::new (storage_) T(std::move(rhs).get()); }
// Destructor
~packed_tuple()
noexcept (std::is_nothrow_destructible_v<T>)
{
if constexpr (not std::is_trivially_destructible_v<T>)
std::destroy_at((T*)storage_);
}
// Copy assignment
packed_tuple& operator=(const packed_tuple& rhs)
noexcept (std::is_nothrow_copy_assignable_v<T>)
{ return (this->get() = rhs.get(), *this); }
// Move assignment
packed_tuple& operator=(packed_tuple&& rhs)
noexcept (std::is_nothrow_move_assignable_v<T>)
{ return (this->get() = std::move(rhs).get(), *this); }
// Getters
const T& get() const & noexcept
{ return *(const T*)storage_; }
T& get() & noexcept
{ return *(T*)storage_; }
const T&& get() const && noexcept
{ return std::move(*(const T*)storage_); }
T&& get() && noexcept
{ return std::move(*(T*)storage_); }
};
// Size of packed_tuple (structured binding)
template <std::size_t N, class... Types>
struct std::tuple_size<packed_tuple<N, Types...>>
: std::integral_constant<std::size_t, sizeof...(Types)>
{ };
// Element type (structured binding)
template <std::size_t I, std::size_t N, class... Types>
struct std::tuple_element<I, packed_tuple<N, Types...>>
: std::tuple_element<I, std::tuple<Types...>>
{ };
@talybin
Copy link
Author

talybin commented Sep 19, 2021

Example. Create tuple that is aligned to short

#include "packed_tuple.hpp"

template <class... Types>
using aligned_tuple = packed_tuple<sizeof(short), Types...>;

@talybin
Copy link
Author

talybin commented Sep 19, 2021

Tests:

static_assert(sizeof(packed_tuple<1, char, int>) == 5);
static_assert(sizeof(packed_tuple<1, int, char>) == 5);

static_assert(sizeof(packed_tuple<2, char, int>) == 6);
static_assert(sizeof(packed_tuple<2, int, char>) == 6);

static_assert(sizeof(packed_tuple<3, char, int>) == 9);
static_assert(sizeof(packed_tuple<3, int, char>) == 9);

static_assert(sizeof(packed_tuple<4, char, int>) == 8);
static_assert(sizeof(packed_tuple<4, int, char>) == 8);

static_assert(sizeof(packed_tuple<1, double, char, int>) == 13);
static_assert(sizeof(packed_tuple<2, double, char, int>) == 14);
static_assert(sizeof(packed_tuple<3, double, char, int>) == 18);
static_assert(sizeof(packed_tuple<4, double, char, int>) == 16);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment