From 9d40e23b19fa49f7863511cec9a7ed2fc3c3f83d Mon Sep 17 00:00:00 2001 From: "Jonathan B. Coe" Date: Tue, 6 Oct 2026 16:59:10 +0100 Subject: [PATCH] Wide pointer narrowing FOLD Build the wide pointer narrowing tutorial with CMake and rename its Bazel target Use Vtable capitalisation in the narrowing tutorial Fix clang-tidy findings in the narrowing tutorial Lower-case wide vtable in the narrowing tutorial comment Name the narrowing tutorial's thunk parameter self Narrow wide pointers with conversion operators on WidePtr --- tutorials/08_wide_pointer_narrowing.cc | 179 +++++++++++++++++++++++++ tutorials/BUILD.bazel | 7 + tutorials/CMakeLists.txt | 6 + 3 files changed, 192 insertions(+) create mode 100644 tutorials/08_wide_pointer_narrowing.cc diff --git a/tutorials/08_wide_pointer_narrowing.cc b/tutorials/08_wide_pointer_narrowing.cc new file mode 100644 index 0000000..1d68615 --- /dev/null +++ b/tutorials/08_wide_pointer_narrowing.cc @@ -0,0 +1,179 @@ +/* Copyright (c) 2025 The XYZ Protocol Authors. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +==============================================================================*/ + +#include + +#include + +// A minimal example of how wide pointers can perform zero-cost narrowing +// conversions when vtables are composed with inheritance. +// +// For non-tutorial code, constructors would need constraining with concepts to +// prevent construction of wide pointers that perform multiple vtable lookups. + +namespace xyz::tutorials::wide_pointer_narrowing { + +// The tests count vtable calls. +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +thread_local std::size_t vtable_calls = 0; + +struct FooVtable { + int (*fn_foo)(void*); +}; + +struct BarVtable { + int (*fn_bar)(void*); +}; + +struct FooPtr { + void* ptr_; + const FooVtable* vtable_; + + template + explicit FooPtr(T* t) : ptr_(t) { + static constexpr FooVtable vtable{ + +[](void* self) { + ++vtable_calls; + return static_cast(self)->foo(); + }, + }; + vtable_ = &vtable; + } + + FooPtr(void* ptr, const FooVtable* vtable) : ptr_(ptr), vtable_(vtable) {} + + int foo() const { return vtable_->fn_foo(ptr_); } +}; + +struct BarPtr { + void* ptr_; + const BarVtable* vtable_; + + template + explicit BarPtr(T* t) : ptr_(t) { + static constexpr BarVtable vtable{ + +[](void* self) { + ++vtable_calls; + return static_cast(self)->bar(); + }, + }; + vtable_ = &vtable; + } + + BarPtr(void* ptr, const BarVtable* vtable) : ptr_(ptr), vtable_(vtable) {} + + int bar() const { return vtable_->fn_bar(ptr_); } +}; + +// Defining the wide vtable in terms of Foo and Bar vtables allows +// narrowing conversions without performing nested vtable calls. +struct WideVtable : FooVtable, BarVtable {}; + +struct WidePtr { + void* ptr_; + const WideVtable* vtable_; + + template + explicit WidePtr(T* t) : ptr_(t) { + static constexpr WideVtable vtable{ + {+[] /* foo */ (void* self) { + ++vtable_calls; + return static_cast(self)->foo(); + }}, + {+[] /* bar */ (void* self) { + ++vtable_calls; + return static_cast(self)->bar(); + }}, + }; + vtable_ = &vtable; + } + + explicit operator FooPtr() const { return {ptr_, vtable_}; } + + explicit operator BarPtr() const { return {ptr_, vtable_}; } + + int foo() const { return vtable_->fn_foo(ptr_); } + + int bar() const { return vtable_->fn_bar(ptr_); } +}; + +struct FooBar { + int foo() { return 0; } + + int bar() { return 1; } +}; + +TEST(TutorialsWidePointerNarrowing, WidePointerCheck) { + vtable_calls = 0; + + FooBar fb; + WidePtr wptr(&fb); + + EXPECT_EQ(wptr.foo(), 0); + EXPECT_EQ(wptr.bar(), 1); + EXPECT_EQ(vtable_calls, 2); +} + +TEST(TutorialsWidePointerNarrowing, FooPointerCheck) { + vtable_calls = 0; + + FooBar fb; + FooPtr foo_ptr(&fb); + EXPECT_EQ(foo_ptr.foo(), 0); + EXPECT_EQ(vtable_calls, 1); +} + +TEST(TutorialsWidePointerNarrowing, BarPointerCheck) { + vtable_calls = 0; + + FooBar fb; + + BarPtr bar_ptr(&fb); + EXPECT_EQ(bar_ptr.bar(), 1); + EXPECT_EQ(vtable_calls, 1); +} + +TEST(TutorialsWidePointerNarrowing, FooPointerNarrowingCheck) { + vtable_calls = 0; + + FooBar fb; + WidePtr wptr(&fb); + + FooPtr foo_ptr(wptr); + EXPECT_EQ(foo_ptr.foo(), 0); + EXPECT_EQ(vtable_calls, 1); + + EXPECT_EQ(foo_ptr.vtable_, static_cast(wptr.vtable_)); +} + +TEST(TutorialsWidePointerNarrowing, BarPointerNarrowingCheck) { + vtable_calls = 0; + + FooBar fb; + WidePtr wptr(&fb); + + BarPtr bar_ptr(wptr); + EXPECT_EQ(bar_ptr.bar(), 1); + EXPECT_EQ(vtable_calls, 1); + + EXPECT_EQ(bar_ptr.vtable_, static_cast(wptr.vtable_)); +} + +} // namespace xyz::tutorials::wide_pointer_narrowing diff --git a/tutorials/BUILD.bazel b/tutorials/BUILD.bazel index c6bc842..dafa485 100644 --- a/tutorials/BUILD.bazel +++ b/tutorials/BUILD.bazel @@ -58,3 +58,10 @@ cc_test( srcs = ["07_wide_pointer_sbo.cc"], deps = ["@googletest//:gtest_main"], ) + +cc_test( + name = "wide_pointer_narrowing_tutorial", + size = "small", + srcs = ["08_wide_pointer_narrowing.cc"], + deps = ["@googletest//:gtest_main"], +) diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt index ac18bf6..16e4d0d 100644 --- a/tutorials/CMakeLists.txt +++ b/tutorials/CMakeLists.txt @@ -44,3 +44,9 @@ xyz_add_test( wide_pointer_sbo_tutorial FILES 07_wide_pointer_sbo.cc) + +xyz_add_test( + NAME + wide_pointer_narrowing_tutorial + FILES + 08_wide_pointer_narrowing.cc)