Skip to content

Commit c517504

Browse files
committed
Add multiple inheritance example with empty base classes
1 parent 2fcc2dd commit c517504

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

tutorials/2_vanishing_this_pointer.cc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2727
// 2. Recovering the owner pointer.
2828
// 3. Ordinary call syntax from a data member.
2929
// 4. What breaks when offset zero is violated, and the guard against it.
30+
// 5. Composing several such members through inheritance.
3031

3132
#include <gtest/gtest.h>
3233

@@ -225,3 +226,74 @@ TEST(ThisPointerOffsetViolated, GuardConfirmsGoodCase) {
225226
}
226227

227228
} // namespace section_4
229+
230+
// ---------------------------------------------------------------------------
231+
// 5. Composing several such members through inheritance.
232+
//
233+
// Owner in section 3 has one wrapper data member, greet. Giving it a
234+
// second, shout, the same way needs each in its own small base struct,
235+
// GreetBase and ShoutBase below, combined by having Owner inherit from
236+
// both.
237+
//
238+
// Each wrapper's own this only has to recover its own base's address:
239+
// GreetWrapper is GreetBase's sole member, so it sits at offset zero of
240+
// GreetBase unconditionally, exactly as section 2's Wrapper sits at
241+
// offset zero of Owner. Getting from GreetBase's address to Owner's is
242+
// then a base-to-derived static_cast, which the compiler performs
243+
// correctly regardless of GreetBase's actual offset within Owner.
244+
// [[no_unique_address]] on greet and shout still matters for size, same
245+
// as sections 1 and 4, but no longer for correctness: with flat data
246+
// members, an unhonored [[no_unique_address]] misplaces every member
247+
// after the first (section 4's failure mode); here it only costs Owner
248+
// some otherwise-unnecessary bytes. This is the technique Duck (cited
249+
// above) uses to build its vtable_wrapper, one base per interface member.
250+
// ---------------------------------------------------------------------------
251+
namespace section_5 {
252+
253+
struct GreetWrapper {
254+
std::string operator()(std::string_view visitor) const;
255+
};
256+
257+
struct GreetBase {
258+
[[no_unique_address]] GreetWrapper greet;
259+
};
260+
261+
struct ShoutWrapper {
262+
std::string operator()(std::string_view visitor) const;
263+
};
264+
265+
struct ShoutBase {
266+
[[no_unique_address]] ShoutWrapper shout;
267+
};
268+
269+
struct Owner : GreetBase, ShoutBase {
270+
std::string name = "World";
271+
};
272+
273+
std::string GreetWrapper::operator()(std::string_view visitor) const {
274+
const auto* base =
275+
static_cast<const GreetBase*>(static_cast<const void*>(this));
276+
const auto* owner = static_cast<const Owner*>(base);
277+
return "Hello, " + std::string(visitor) + ", from " + owner->name + "!";
278+
}
279+
280+
std::string ShoutWrapper::operator()(std::string_view visitor) const {
281+
const auto* base =
282+
static_cast<const ShoutBase*>(static_cast<const void*>(this));
283+
const auto* owner = static_cast<const Owner*>(base);
284+
return std::string(visitor) + ", " + owner->name + " SHOUTS HELLO!";
285+
}
286+
287+
TEST(ThisPointerInheritance, EachMemberReachesTheSameOwner) {
288+
Owner owner;
289+
owner.name = "Owner";
290+
291+
EXPECT_EQ(owner.greet("Reader"), "Hello, Reader, from Owner!");
292+
EXPECT_EQ(owner.shout("Reader"), "Reader, Owner SHOUTS HELLO!");
293+
}
294+
295+
TEST(ThisPointerInheritance, NoExtraStorage) {
296+
EXPECT_EQ(sizeof(Owner), sizeof(std::string));
297+
}
298+
299+
} // namespace section_5

0 commit comments

Comments
 (0)