Skip to content

Instantly share code, notes, and snippets.

@seiren-naru-shirayuri
Last active July 14, 2025 02:10
Show Gist options
  • Select an option

  • Save seiren-naru-shirayuri/73c1b217028bdc4e576dbc65906a3459 to your computer and use it in GitHub Desktop.

Select an option

Save seiren-naru-shirayuri/73c1b217028bdc4e576dbc65906a3459 to your computer and use it in GitHub Desktop.
A C++ any implementation with tagged pointer in C++20 syntax.

A C++ any implementation with tagged pointer in C++20 syntax.

Difference from std::any:

  1. All names except partial specialization of class templates in std inhabit in global namespace instead of std namespace.

  2. Supports MSVC and GCC (probably clang) on x86 and x64 only, ARM and ARM64 not tested.

#pragma once
#include <cstddef>
#include <cassert>
#include <new>
#include <typeinfo>
#include <utility>
#include <initializer_list>
#include <type_traits>
#include <exception>
#include "tagged_ptr.hpp"
class bad_any_cast : public std::bad_cast
{
public:
const char* what() const noexcept override
{
return message;
}
private:
inline static constexpr char message[] = "bad any_cast";
};
class any
{
private:
alignas(std::max_align_t) std::byte m_buffer[sizeof(std::max_align_t)];
tagged_ptr<const std::type_info> m_ptypeinfo;
enum class func_op_t { copy, soo_copy, soo_move, soo_destruct, destroy };
void (*m_func_table)(func_op_t, void*, std::byte*);
template <typename T>
inline static void m_func_table_init(func_op_t func_op, void* pobj, std::byte* addr)
{
switch (func_op)
{
case func_op_t::copy:
new(addr) T*(new T(**std::launder(reinterpret_cast<T**>(pobj))));
break;
case func_op_t::soo_copy:
new(addr) T(*std::launder(reinterpret_cast<T*>(pobj)));
break;
case func_op_t::soo_move:
new(addr) T(std::move(*std::launder(reinterpret_cast<T*>(pobj))));
break;
case func_op_t::soo_destruct:
std::launder(reinterpret_cast<T*>(pobj))->~T();
break;
case func_op_t::destroy:
delete *std::launder(reinterpret_cast<T**>(pobj));
break;
default:
assert(0 && "Invalid func_op.");
}
}
void copy(const void* pobj, std::byte* addr) const
{
m_func_table(func_op_t::copy, const_cast<void*>(pobj), addr);
}
void soo_copy(const void* pobj, std::byte* addr) const
{
m_func_table(func_op_t::soo_copy, const_cast<void*>(pobj), addr);
}
void soo_move(void* pobj, std::byte* addr) noexcept
{
m_func_table(func_op_t::soo_move, pobj, addr);
}
void soo_destruct(void* pobj) noexcept
{
m_func_table(func_op_t::soo_destruct, pobj, nullptr);
}
void destroy(void* pobj) noexcept
{
m_func_table(func_op_t::destroy, pobj, nullptr);
}
template <typename T>
struct uses_soo : std::bool_constant<std::is_nothrow_move_constructible_v<T> && (alignof(T) <= alignof(decltype(m_buffer))) && (sizeof(T) <= sizeof(m_buffer))> {};
template <typename T>
inline static constexpr bool uses_soo_v = uses_soo<T>::value;
template <typename T, typename ...Args>
std::decay_t<T>& construct(Args&&... args)
{
std::decay_t<T>* ret;
if constexpr (uses_soo_v<std::decay_t<T>>)
{
ret = new(m_buffer) std::decay_t<T>(std::forward<Args>(args)...);
}
else
{
ret = *(new(m_buffer) std::decay_t<T>*(new std::decay_t<T>(std::forward<Args>(args)...)));
m_ptypeinfo.set_tag(1);
}
m_ptypeinfo = &typeid(std::decay_t<T>);
m_func_table = m_func_table_init<std::decay_t<T>>;
return *ret;
}
public:
constexpr any() noexcept : m_ptypeinfo(nullptr) {}
any(const any& other) : m_ptypeinfo(other.m_ptypeinfo)
{
if (m_ptypeinfo)
{
if (m_ptypeinfo.has_tag())
{
other.copy(other.m_buffer, m_buffer);
}
else
{
other.soo_copy(other.m_buffer, m_buffer);
}
m_func_table = other.m_func_table;
}
}
any(any&& other) noexcept : any()
{
swap(other);
}
template <typename T>
requires std::conjunction_v<std::negation<std::is_same<std::decay_t<T>, any>>, std::negation<std::is_same<std::decay_t<T>, std::in_place_type_t<T>>>, std::is_copy_constructible<std::decay_t<T>>>
any(T&& t)
{
construct<T>(std::forward<T>(t));
}
template <typename T, typename ...Args>
requires std::conjunction_v<std::is_constructible<std::decay_t<T>, Args...>, std::is_copy_constructible<std::decay_t<T>>>
explicit any(std::in_place_type_t<T>, Args&&... args)
{
construct<T>(std::forward<Args>(args)...);
}
template <typename T, typename U, typename ...Args>
requires std::conjunction_v<std::is_constructible<std::decay_t<T>, std::initializer_list<U>&, Args...>, std::is_copy_constructible<std::decay_t<T>>>
explicit any(std::in_place_type_t<T>, std::initializer_list<U> il, Args&&... args)
{
construct<T>(il, std::forward<Args>(args)...);
}
~any()
{
reset();
}
any& operator=(const any& other)
{
if (this != &other)
{
any(other).swap(*this);
}
return *this;
}
any& operator=(any&& other) noexcept
{
if (this != &other)
{
swap(other);
}
return *this;
}
template <typename T>
requires std::conjunction_v<std::negation<std::is_same<std::decay_t<T>, any>>, std::is_copy_constructible<std::decay_t<T>>>
any& operator=(T&& t)
{
any(std::forward<T>(t)).swap(*this);
return *this;
}
template <typename T, typename ...Args>
requires std::conjunction_v<std::is_constructible<std::decay_t<T>, Args...>, std::is_copy_constructible<std::decay_t<T>>>
std::decay_t<T>& emplace(Args&&... args)
{
reset();
return construct<T>(std::forward<Args>(args)...);
}
template <typename T, typename U, typename ...Args>
requires std::conjunction_v<std::is_constructible<std::decay_t<T>, std::initializer_list<U>&, Args...>, std::is_copy_constructible<std::decay_t<T>>>
std::decay_t<T>& emplace(std::initializer_list<U> il, Args&&... args)
{
reset();
return construct<T>(il, std::forward<Args>(args)...);
}
void reset() noexcept
{
if (m_ptypeinfo)
{
if (m_ptypeinfo.has_tag())
{
destroy(m_buffer);
}
else
{
soo_destruct(m_buffer);
}
m_ptypeinfo.clear();
}
}
void swap(any& other) noexcept
{
if (m_ptypeinfo && other.m_ptypeinfo)
{
alignas(decltype(m_buffer)) std::byte tempbuffer[sizeof(m_buffer)];
if (m_ptypeinfo.has_tag() && other.m_ptypeinfo.has_tag())
{
std::swap(m_buffer, other.m_buffer);
}
else if (m_ptypeinfo.has_tag())
{
other.soo_move(other.m_buffer, tempbuffer);
other.soo_destruct(other.m_buffer);
std::swap(m_buffer, other.m_buffer);
other.soo_move(tempbuffer, m_buffer);
other.soo_destruct(tempbuffer);
}
else if (other.m_ptypeinfo.has_tag())
{
soo_move(m_buffer, tempbuffer);
soo_destruct(m_buffer);
std::swap(m_buffer, other.m_buffer);
soo_move(tempbuffer, other.m_buffer);
soo_destruct(tempbuffer);
}
else
{
soo_move(m_buffer, tempbuffer);
soo_destruct(m_buffer);
other.soo_move(other.m_buffer, m_buffer);
other.soo_destruct(other.m_buffer);
soo_move(tempbuffer, other.m_buffer);
soo_destruct(tempbuffer);
}
::swap(m_ptypeinfo, other.m_ptypeinfo);
std::swap(m_func_table, other.m_func_table);
}
else if (m_ptypeinfo)
{
if (m_ptypeinfo.has_tag())
{
std::swap(m_buffer, other.m_buffer);
}
else
{
soo_move(m_buffer, other.m_buffer);
soo_destruct(m_buffer);
}
::swap(m_ptypeinfo, other.m_ptypeinfo);
std::swap(m_func_table, other.m_func_table);
}
else if (other.m_ptypeinfo)
{
if (other.m_ptypeinfo.has_tag())
{
std::swap(m_buffer, other.m_buffer);
}
else
{
other.soo_move(other.m_buffer, m_buffer);
other.soo_destruct(other.m_buffer);
}
::swap(m_ptypeinfo, other.m_ptypeinfo);
std::swap(m_func_table, other.m_func_table);
}
else
{
return;
}
}
bool has_value() const noexcept
{
return m_ptypeinfo != nullptr;
}
const std::type_info& type() const noexcept
{
return m_ptypeinfo ? *m_ptypeinfo : typeid(void);
}
template <typename T>
friend T any_cast(const any&);
template <typename T>
friend T any_cast(any&);
template <typename T>
friend T any_cast(any&&);
template <typename T>
friend const T* any_cast(const any*) noexcept;
template <typename T>
friend T* any_cast(any*) noexcept;
friend void swap(any&, any&) noexcept;
};
template <typename T>
const T* any_cast(const any* op) noexcept
{
static_assert(!std::is_void_v<T>, "T cannot be void.");
if (op)
{
if (typeid(T) == op->type())
{
if (op->m_ptypeinfo.has_tag())
{
return *std::launder(reinterpret_cast<const T**>(op->m_buffer));
}
else
{
return std::launder(reinterpret_cast<const T*>(op->m_buffer));
}
}
else
{
return nullptr;
}
}
else
{
return nullptr;
}
}
template <typename T>
T* any_cast(any* op) noexcept
{
static_assert(!std::is_void_v<T>, "T cannot be void.");
if (op)
{
if (typeid(T) == op->type())
{
if (op->m_ptypeinfo.has_tag())
{
return *std::launder(reinterpret_cast<T**>(op->m_buffer));
}
else
{
return std::launder(reinterpret_cast<T*>(op->m_buffer));
}
}
else
{
return nullptr;
}
}
else
{
return nullptr;
}
}
template <typename T>
T any_cast(const any& op)
{
if (auto r = any_cast<std::remove_cvref_t<T>>(&op); r)
{
return static_cast<T>(*r);
}
else
{
throw bad_any_cast{};
}
}
template <typename T>
T any_cast(any& op)
{
if (auto r = any_cast<std::remove_cvref_t<T>>(&op); r)
{
return static_cast<T>(*r);
}
else
{
throw bad_any_cast{};
}
}
template <typename T>
T any_cast(any&& op)
{
if (auto r = any_cast<std::remove_cvref_t<T>>(&op); r)
{
return static_cast<T>(std::move(*r));
}
else
{
throw bad_any_cast{};
}
}
void swap(any& lhs, any& rhs) noexcept
{
lhs.swap(rhs);
}
template <typename T, typename ...Args>
any make_any(Args&&... args)
{
return any(std::in_place_type<T>, std::forward<Args>(args)...);
}
template <typename T, typename U, typename ...Args>
any make_any(std::initializer_list<U> il, Args&&... args)
{
return any(std::in_place_type<T>, il, std::forward<Args>(args)...);
}
#pragma once
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <bit>
#include <array>
#include <limits>
#include <memory>
#include <utility>
#include <type_traits>
#include <compare>
#include <functional>
/// clang also defines __GNUC__ on linux and __MSC_VER on Windows.
#if defined(__MSC_VER)
#if !(defined(_M_IX86) || defined(_M_X64))
#error unsupported architecture
#endif
#elif defined(__GNUC__)
#if !(defined(__i386__) || defined(__amd64__))
#error unsupported architecture
#endif
#else
#error unsupported compiler
#endif
/// A non-owning smart pointer that uses the unused lower bits of an aligned
/// pointer to store a small integer tag.
///
/// @note The provided pointer must be sufficiently aligned to have enough
/// free bits to store the tag. Passing a misaligned pointer is a logic error
/// and will trigger an assertion in debug builds, but is undefined behavior
/// in release builds.
template <typename T, std::size_t A = alignof(T)>
requires std::is_object_v<T> && (std::has_single_bit(A))
class tagged_ptr
{
private:
inline static constexpr std::uintptr_t m_tag_mask = A - 1;
inline static constexpr std::uintptr_t m_pointer_mask = ~m_tag_mask;
std::uintptr_t cast_pointer(T* ptr) const noexcept
{
assert(((reinterpret_cast<std::uintptr_t>(ptr) & m_tag_mask) == 0) && "Pointer is not sufficiently aligned.");
return reinterpret_cast<std::uintptr_t>(ptr);
}
std::uintptr_t m_data;
public:
using pointer = T*;
using element_type = T;
using tag_type = std::uintptr_t;
inline static constexpr std::size_t alignment = A;
inline static constexpr std::uintptr_t tag_mask = m_tag_mask;
inline static constexpr std::uintptr_t tag_bits = std::popcount(m_tag_mask);
inline static constexpr std::uintptr_t pointer_mask = m_pointer_mask;
inline static constexpr std::uintptr_t pointer_bits = std::popcount(pointer_mask);
static_assert(tag_bits != 0, "T has not space for the tag.");
constexpr tagged_ptr() noexcept : m_data(0) {}
constexpr tagged_ptr(std::nullptr_t) noexcept : tagged_ptr() {}
tagged_ptr(pointer ptr, tag_type tag = 0) noexcept : m_data(cast_pointer(ptr) | (tag & tag_mask)) {}
tagged_ptr& operator=(pointer ptr) noexcept
{
set_pointer(ptr);
return *this;
}
[[nodiscard]] pointer get_pointer() const noexcept
{
return reinterpret_cast<pointer>(m_data & pointer_mask);
}
[[nodiscard]] tag_type get_tag() const noexcept
{
return m_data & tag_mask;
}
[[nodiscard]] std::pair<pointer, tag_type> get() const noexcept
{
return { get_pointer(), get_tag() };
}
[[nodiscard]] tag_type get_raw() const noexcept
{
return m_data;
}
[[nodiscard]] bool has_pointer() const noexcept
{
return get_pointer() != nullptr;
}
[[nodiscard]] bool has_tag() const noexcept
{
return get_tag() != 0;
}
[[nodiscard]] bool has_value() const noexcept
{
return m_data != 0;
}
explicit operator bool() const noexcept
{
return has_pointer();
}
void set_pointer(pointer ptr) noexcept
{
m_data = cast_pointer(ptr) | (m_data & tag_mask);
}
void set_tag(tag_type tag) noexcept
{
m_data = (m_data & pointer_mask) | (tag & tag_mask);
}
void set(pointer ptr, tag_type tag) noexcept
{
m_data = cast_pointer(ptr) | (tag & tag_mask);
}
void clear_pointer() noexcept
{
set_pointer(nullptr);
}
void clear_tag() noexcept
{
set_tag(0);
}
void clear() noexcept
{
m_data = 0;
}
std::add_lvalue_reference_t<T> operator*() const noexcept
{
assert(has_pointer());
return *get_pointer();
}
pointer operator->() const noexcept
{
assert(has_pointer());
return get_pointer();
}
T& operator[](std::size_t index) const
{
assert(has_pointer());
return get_pointer()[index];
}
void swap(tagged_ptr& other) noexcept
{
std::swap(m_data, other.m_data);
}
void swap_pointer(tagged_ptr& other) noexcept
{
auto data_tmp = m_data;
m_data = (other.m_data & pointer_mask) | (m_data & tag_mask);
other.m_data = (data_tmp & pointer_mask) | (other.m_data & tag_mask);
}
void swap_tag(tagged_ptr& other) noexcept
{
auto data_tmp = m_data;
m_data = (m_data & pointer_mask) | (other.m_data & tag_mask);
other.m_data = (other.m_data & pointer_mask) | (data_tmp & tag_mask);
}
template <typename U, std::size_t A0>
friend void swap(tagged_ptr<U, A0>&, tagged_ptr<U, A0>&) noexcept;
template <typename U, std::size_t A0>
friend void swap_pointer(tagged_ptr<U, A0>&, tagged_ptr<U, A0>&) noexcept;
template <typename U, std::size_t A0>
friend void swap_tag(tagged_ptr<U, A0>&, tagged_ptr<U, A0>&) noexcept;
template <typename T1, std::size_t A1, typename T2, std::size_t A2>
friend bool operator==(const tagged_ptr<T1, A1>&, const tagged_ptr<T2, A2>&) noexcept;
template <typename U, std::size_t A0>
friend bool operator==(const tagged_ptr<U, A0>&, std::nullptr_t) noexcept;
template <typename T1, std::size_t A1, typename T2, std::size_t A2>
requires std::three_way_comparable_with<typename tagged_ptr<T1, A1>::pointer, typename tagged_ptr<T2, A2>::pointer>
friend std::compare_three_way_result_t<typename tagged_ptr<T1, A1>::pointer, typename tagged_ptr<T2, A2>::pointer> operator<=>(const tagged_ptr<T1, A1>&, const tagged_ptr<T2, A2>&) noexcept;
template <typename U, std::size_t A0>
requires std::three_way_comparable<typename tagged_ptr<U, A0>::pointer>
friend std::compare_three_way_result_t<typename tagged_ptr<U, A0>::pointer> operator<=>(const tagged_ptr<U, A0>&, std::nullptr_t) noexcept;
};
/// Hashes the tagged_ptr.
/// @note To be consistent with operator==, only the pointer part is used in the hash calculation.
template <typename T, std::size_t N>
struct std::hash<tagged_ptr<T, N>>
{
std::size_t operator()(const tagged_ptr<T, N>& op) const noexcept
{
return std::hash<typename tagged_ptr<T, N>::pointer>{}(op.get_pointer());
}
};
template <typename T, std::size_t A>
void swap(tagged_ptr<T, A>& lhs, tagged_ptr<T, A>& rhs) noexcept
{
return lhs.swap(rhs);
}
template <typename T, std::size_t A>
void swap_pointer(tagged_ptr<T, A>& lhs, tagged_ptr<T, A>& rhs) noexcept
{
return lhs.swap_pointer(rhs);
}
template <typename T, std::size_t A>
void swap_tag(tagged_ptr<T, A>& lhs, tagged_ptr<T, A>& rhs) noexcept
{
return lhs.swap_tag(rhs);
}
template <typename T1, std::size_t A1, typename T2, std::size_t A2>
bool operator==(const tagged_ptr<T1, A1>& lhs, const tagged_ptr<T2, A2>& rhs) noexcept
{
return lhs.get_pointer() == rhs.get_pointer();
}
template <typename T, std::size_t A>
bool operator==(const tagged_ptr<T, A>& op, std::nullptr_t) noexcept
{
return op.get_pointer() == nullptr;
}
template <typename T1, std::size_t A1, typename T2, std::size_t A2>
requires std::three_way_comparable_with<typename tagged_ptr<T1, A1>::pointer, typename tagged_ptr<T2, A2>::pointer>
std::compare_three_way_result_t<typename tagged_ptr<T1, A1>::pointer, typename tagged_ptr<T2, A2>::pointer> operator<=>(const tagged_ptr<T1, A1>& lhs, const tagged_ptr<T2, A2>& rhs) noexcept
{
return lhs.get_pointer() <=> rhs.get_pointer();
}
template <typename T, std::size_t A>
requires std::three_way_comparable<typename tagged_ptr<T, A>::pointer>
std::compare_three_way_result_t<typename tagged_ptr<T, A>::pointer> operator<=>(const tagged_ptr<T, A>& op, std::nullptr_t) noexcept
{
return op.get_pointer() <=> nullptr;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment