Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code_testing.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build and run Tests

on: [ push ]
on: [ pull_request ]

concurrency:
group: build-and-run-tests-${{ github.workflow }}-${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-conan-branch-package.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Publish Conan branch package

on: [ push ]
on: [ pull_request ]

concurrency:
group: publish-conan-branch-package-${{ github.workflow }}-${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.24)

project(
dice-template-library
VERSION 2.4.0
VERSION 2.5.0
DESCRIPTION
"This template library is a collection of template-oriented code that we, the Data Science Group at UPB, found pretty handy. It contains: `switch_cases` (Use runtime values in compile-time context), `integral_template_tuple` (Create a tuple-like structure that instantiates a template for a range of values), `integral_template_variant` (A wrapper type for `std::variant` guarantees to only contain variants of the form `T<IX>` and `for_{types,values,range}` (Compile time for loops for types, values or ranges))."
HOMEPAGE_URL "https://dice-research.org/")
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ It contains:
- `functional`: Extensions for `<functional>`. Currently, contains a `bind_front` implementation with constexpr function argument.
- `DICE_DBG`: Prints and returns the value of a given expression for quick and dirty debugging.
- `opt_min`/`opt_max`/`opt_minmax`: Optional-aware min/max/minmax algorithms that treat `std::nullopt` as "no value".
- `pointer_tag_pair`: A pointer and value pair that stores a small integer tag in the pointer alignment bits based on the API
from [P3125 (constexpr pointer tagging)](https://wg21.link/P3125).

## Usage

Expand Down Expand Up @@ -259,7 +261,7 @@ A C++23 compatible compiler. Code was only tested on x86_64.
## Include it in your projects
### Conan
You can use it with [conan](https://conan.io/).
To do so, you need to add `dice-template-library/2.4.0` to the `[requires]` section of your conan file.
To do so, you need to add `dice-template-library/2.5.0` to the `[requires]` section of your conan file.

## Build and Run Tests and Examples

Expand Down
7 changes: 7 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,10 @@ target_link_libraries(example_opt_minmax
dice-template-library::dice-template-library
)

add_executable(example_pointer_tag_pair
example_pointer_tag_pair.cpp)
target_link_libraries(example_pointer_tag_pair
PRIVATE
dice-template-library::dice-template-library
)

38 changes: 38 additions & 0 deletions examples/example_pointer_tag_pair.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <cassert>

#include <dice/template-library/pointer_tag_pair.hpp>

namespace dtl = dice::template_library;

// alignof(node) == 8, so the lowest 3 bits of any node* are zero
struct alignas(8) node {
int value;
};

enum class letters : unsigned {
A = 0,
B = 1,
C = 2,
};

int main() {
node n{42};

// By default, the number of tag bits is deduced from alignof(T).
// For `node` that gives 3 bits (values 0..7).
using tagged_node = dtl::pointer_tag_pair<node, unsigned>;
static_assert(tagged_node::bits_requested == 3);

tagged_node p{&n, 5};
assert(p.pointer() == &n);
assert(p.pointer()->value == 42);
assert(p.tag() == 5);

auto const [ptr, tag] = p;
assert(ptr == &n);
assert(tag == 5);

// Enums with an unsigned underlying type are valid tags too.
dtl::pointer_tag_pair<node, letters> q{&n, letters::C};
assert(q.tag() == letters::C);
}
148 changes: 148 additions & 0 deletions include/dice/template-library/pointer_tag_pair.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#ifndef DICE_TEMPLATE_LIBRARY_POINTERTAGPAIR_HPP
#define DICE_TEMPLATE_LIBRARY_POINTERTAGPAIR_HPP

#include <bit>
#include <cassert>
#include <compare>
#include <cstddef>
#include <tuple>
#include <utility>

#include <dice/template-library/type_traits.hpp>

namespace dice::template_library {
namespace detail_pointer_tag_pair {
template<typename T>
concept taggable_pointee = !std::is_function_v<T>;

template<typename Pointer, typename T>
concept taggable_pointer = std::is_pointer_v<Pointer> && std::convertible_to<Pointer, T *>;

template<typename Tag>
concept taggable_pointer_tag = std::unsigned_integral<Tag> || (std::is_enum_v<Tag> && std::unsigned_integral<std::underlying_type_t<Tag>>);

template<typename T, unsigned Alignment = 0>
consteval unsigned taggable_bits_available() {
if constexpr (Alignment != 0) {
return std::countr_zero(Alignment);
} else {
if constexpr (std::is_void_v<T>) {
return 0;
} else {
return std::countr_zero(alignof(T));
}
}
}
} // namespace detail_pointer_tag_pair

template<detail_pointer_tag_pair::taggable_pointee T,
detail_pointer_tag_pair::taggable_pointer_tag Tag,
unsigned BitsRequested = detail_pointer_tag_pair::taggable_bits_available<T>()>
struct pointer_tag_pair {
private:
static constexpr uintptr_t ptr_mask = static_cast<uintptr_t>(-1) << BitsRequested;
static constexpr uintptr_t tag_mask = (static_cast<uintptr_t>(1) << BitsRequested) - 1;
static constexpr size_t alignment_required = static_cast<size_t>(1) << BitsRequested;

uintptr_t value_ = 0;

explicit constexpr pointer_tag_pair(uintptr_t const value) noexcept
: value_{value} {
}

public:
using pointer_type = T *;
using tagged_pointer_type = copy_cv_t<T, void> *;
using tag_type = Tag;

static constexpr unsigned bits_requested = BitsRequested;

constexpr pointer_tag_pair() noexcept = default;

constexpr pointer_tag_pair(std::nullptr_t, Tag const tag = Tag{}) noexcept
: pointer_tag_pair{static_cast<uintptr_t>(tag)} {
assert(static_cast<uintptr_t>(tag) <= tag_mask);
}

template<detail_pointer_tag_pair::taggable_pointer<T> P>
requires (BitsRequested <= detail_pointer_tag_pair::taggable_bits_available<std::remove_pointer_t<P>>())
pointer_tag_pair(P const pointer, Tag const tag) noexcept
: pointer_tag_pair{reinterpret_cast<uintptr_t>(pointer) | static_cast<uintptr_t>(tag)} {
assert((reinterpret_cast<uintptr_t>(pointer) & tag_mask) == 0);
assert(static_cast<uintptr_t>(tag) <= tag_mask);
}

template<unsigned PromisedAlignment, detail_pointer_tag_pair::taggable_pointer<T> P>
requires (BitsRequested <= detail_pointer_tag_pair::taggable_bits_available<std::remove_pointer_t<P>, PromisedAlignment>())
[[nodiscard]] static pointer_tag_pair from_overaligned(P const pointer, Tag const tag) noexcept {
assert((reinterpret_cast<uintptr_t>(pointer) & tag_mask) == 0);
assert(static_cast<uintptr_t>(tag) <= tag_mask);

return pointer_tag_pair{reinterpret_cast<uintptr_t>(pointer) | static_cast<uintptr_t>(tag)};
}

[[nodiscard]] static pointer_tag_pair from_tagged(tagged_pointer_type const pointer) noexcept {
return pointer_tag_pair{reinterpret_cast<uintptr_t>(pointer)};
}

[[nodiscard]] tagged_pointer_type tagged_pointer() const noexcept {
return reinterpret_cast<tagged_pointer_type>(value_);
}

[[nodiscard]] T *pointer() const noexcept {
return reinterpret_cast<T *>(value_ & ptr_mask);
}

[[nodiscard]] constexpr Tag tag() const noexcept {
return static_cast<Tag>(value_ & tag_mask);
}

friend constexpr void swap(pointer_tag_pair &lhs, pointer_tag_pair &rhs) noexcept {
std::swap(lhs.value_, rhs.value_);
}

constexpr std::strong_ordering operator<=>(pointer_tag_pair const &other) const noexcept = default;
};

template<size_t I, typename T, typename Tag, unsigned BitsRequested>
constexpr std::tuple_element_t<I, pointer_tag_pair<T, Tag, BitsRequested>>
get(pointer_tag_pair<T, Tag, BitsRequested> const pointer) {
if constexpr (I == 0) {
return pointer.pointer();
} else {
return pointer.tag();
}
}
} // namespace dice::template_library

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_size<dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> > {
static constexpr size_t value = 2;
};

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_size<dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> const> {
static constexpr size_t value = 2;
};

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_element<0, dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> > {
using type = T *;
};

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_element<1, dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> > {
using type = Tag;
};

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_element<0, dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> const> {
using type = T *;
};

template<typename T, typename Tag, unsigned BitsRequested>
struct std::tuple_element<1, dice::template_library::pointer_tag_pair<T, Tag, BitsRequested> const> {
using type = Tag;
};

#endif // DICE_TEMPLATE_LIBRARY_POINTERTAGPAIR_HPP
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ custom_add_test(tests_polymorphic_allocator)
add_executable(tests_overloaded tests_overloaded.cpp)
custom_add_test(tests_overloaded)

add_executable(tests_pointer_tag_pair tests_pointer_tag_pair.cpp)
custom_add_test(tests_pointer_tag_pair)

add_executable(tests_defer tests_defer.cpp)
custom_add_test(tests_defer)

Expand Down
28 changes: 28 additions & 0 deletions tests/tests_pointer_tag_pair.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

#include <dice/template-library/pointer_tag_pair.hpp>

TEST_SUITE("pointer_tag_pair") {
using namespace dice::template_library;

struct alignas(4) s {
};

using pair_type = pointer_tag_pair<s, unsigned>;
static_assert(pair_type::bits_requested == 2);

static s array[2];

TEST_CASE("constructor") {
auto const [p, t] = pair_type{array, 3};
REQUIRE(p == array);
REQUIRE(t == 3);
}

TEST_CASE("nullptr conversion") {
pair_type x = nullptr;
REQUIRE_EQ(x.pointer(), nullptr);
REQUIRE_EQ(x.tag(), 0);
}
}
Loading