{"id":429,"date":"2021-01-26T11:24:28","date_gmt":"2021-01-26T11:24:28","guid":{"rendered":"https:\/\/tbekk.com\/devstream\/?p=429"},"modified":"2021-02-02T10:42:19","modified_gmt":"2021-02-02T10:42:19","slug":"write-a-c-class-that-iterates-over-its-base-classes","status":"publish","type":"post","link":"https:\/\/tbekk.com\/devstream\/blog\/2021\/01\/26\/write-a-c-class-that-iterates-over-its-base-classes\/","title":{"rendered":"Write a C++ class that iterates over its base classes?"},"content":{"rendered":"\n<p><em>Here is an interesting article showing how you can use modern C++ features (C++17 and C++20) for automating the call of functions shared by base classes. <\/em><\/p>\n\n\n\n<p><em>Tested on Compiler Explorer (<a href=\"https:\/\/godbolt.org\/\">https:\/\/godbolt.org\/<\/a>)<\/em> <em>using the following options <strong>[-std=c++20 -O2 -Wall -pedantic -pthread]<\/strong><\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Suppose you have a class with multiple base classes, and you want to invoke a method on all of the base classes.<\/p>\n\n\n\n<p>For example, say we have&nbsp;<code>Pillow<\/code>&nbsp;and&nbsp;<code>Radio<\/code>&nbsp;classes:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class Pillow\n{\npublic:\n    int price();\n    int weight();\n    void refurbish(int level);\n};\n\nclass Radio\n{\npublic:\n    int price();\n    int weight();\n    void refurbish(int level);\n};\n<\/pre>\n\n\n\n<p>And you want to create a&nbsp;<code>PillowRadio<\/code>, which is a combination pillow and radio. It is basically a pillow and a radio glued together. Okay, this is kind of ridiculous because there is no such thing as a pillow-radio,\u00b9 but let\u2019s go along with it.<\/p>\n\n\n\n<p>We would like the PillowRadio class to go something like this, assuming there were some way to iterate over the base classes, for which I have made up some hypothetical syntax.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class PillowRadio : public Pillow, public Radio\n{\npublic:\n    int price()\n    {\n        int total = 0;\n        for (typename T : base_classes_of(this)) {\n            total += T::price();\n        }\n        return total + 10; \/* extra 10 for packaging *\/\n    }\n\n    int weight()\n    {\n        int total = 0;\n        for (typename T : base_classes_of(this)) {\n            total += T::weight();\n        }\n        return total + 5; \/* extra 5 for packaging *\/\n    }\n\n    void refurbish(int level)\n    {\n        for (typename T : base_classes_of(this)) {\n            T::refurbish(level);\n        }\n    }\n};\n<\/pre>\n\n\n\n<p>The point is that you may have cases where you want to iterate over your base classes and aggregate the results.<\/p>\n\n\n\n<p>So how do you do this?<\/p>\n\n\n\n<p>C++ doesn\u2019t provide this degree of reflection but you can simulate it by introducing a helper class.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typename... bases&gt;\nstruct Aggregator : bases...\n{\n    int price()\n    {\n        return (0 + ... + bases::price());\n    }\n\n    int weight()\n    {\n        return (0 + ... + bases::weight());\n    }\n\n    void refurbish(int level)\n    {\n        (bases::refurbish(level), ...);\n    }\n};\n\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\n{\npublic:\n    int price()\n    {\n        return Aggregator::price() + 10; \/* extra 10 for packaging *\/\n    }\n\n    int weight()\n    {\n        return Aggregator::weight() + 5; \/* extra 5 for packaging *\/\n    }\n\n    \/* inherit refurbish from Aggregator *\/\n};\n<\/pre>\n\n\n\n<p>How does this work?<\/p>\n\n\n\n<p>The&nbsp;<code>Aggregator<\/code>&nbsp;class is given a list of base classes, and it dutifully derives from them. So that solves the first problem: Deriving from an&nbsp;<code>Aggregator<\/code>&nbsp;causes you to derive from all of the specified base classes.<\/p>\n\n\n\n<p>The methods on&nbsp;<code>Aggregator<\/code>&nbsp;use&nbsp;<a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/fold\" target=\"_blank\" rel=\"noreferrer noopener\">fold expressions<\/a>&nbsp;which iterate over the template type parameters and combine the results in some way.<\/p>\n\n\n\n<p>For the case of&nbsp;<code>refurbish<\/code>, we don\u2019t actually have any results to combine; we just want to invoke&nbsp;<code>refurbish<\/code>&nbsp;on each base class, so we use the comma operator to throw the results away after invoking each method. Fortunately,&nbsp;<code>refurbish<\/code>&nbsp;returns&nbsp;<code>void<\/code>, so we don\u2019t have to worry about somebody doing a&nbsp;<a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200904-00\/?p=104172\">sneaky overload of the comma operator<\/a>.<\/p>\n\n\n\n<p>Of course, this&nbsp;<code>Aggregator<\/code>&nbsp;is tightly coupled to the methods of its base classes. Maybe we can generalize it.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typename... bases&gt;\nstruct Aggregator : bases...\n{\n    template&lt;typename Visitor&gt;\n    void for_each_base(Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases&amp;&gt;(*this))), ...);\n    }\n};\n<\/pre>\n\n\n\n<p>The&nbsp;<code class=\"\">for_each_base<\/code>&nbsp;method takes a visitor functor and calls it once for each base class. We cast the result to&nbsp;<code>void<\/code>&nbsp;so that we can&nbsp;<a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200904-00\/?p=104172\">safely use the comma fold operator<\/a>&nbsp;to throw the results away after each call of the visitor.<\/p>\n\n\n\n<p>Now we can implement the aggregator methods for our&nbsp;<code class=\"\">PillowRadio<\/code>&nbsp;class.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class PillowRadio : Aggregator&lt;Pillow, Radio&gt;\n{\npublic:\n    int price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        for_each_base([&amp;](auto&amp;&amp; base) { total += base.price(); });\n        return total;\n    }\n\n    int weight()\n    {\n        int total = 5; \/* extra 5 for packaging *\/\n        for_each_base([&amp;](auto&amp;&amp; base) { total += base.weight(); });\n        return total;\n    }\n\n    void refurbish(int level)\n    {\n        for_each_base([&amp;](auto&amp;&amp; base) { base.refurbish(level); });\n    }\n};\n<\/pre>\n\n\n\n<p>Okay, but what about static members?<\/p>\n\n\n\n<p>Since function parameters cannot be types, we have to encode the type in the parameter somehow, say by passing a suitably-cast null pointer.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typename... bases&gt;\nstruct Aggregator : bases...\n{\n    template&lt;typename Visitor&gt;\n    void for_each_base(Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases&amp;&gt;(*this))), ...);\n    }\n\n    template&lt;typename Visitor&gt;\n    static void static_for_each_base(Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases*&gt;(nullptr))), ...);\n    }\n};\n<\/pre>\n\n\n\n<p>This time, the lambda gets a null pointer of the appropriate type. You can then access static members via that strongly-typed null pointer.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class Pillow\n{\npublic:\n    static int list_price();\n};\n\nclass Radio\n{\npublic:\n    static int list_price();\n};\n\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\n{\npublic:\n    static int list_price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        static_for_each_base([&amp;](auto* base) {\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\n            total += Base::list_price();\n        });\n        return total;\n    }\n};\n<\/pre>\n\n\n\n<p>Even though the visitor is given a pointer, that pointer is always null. It is useful only for its type information, not for its value.<\/p>\n\n\n\n<p>It is&nbsp;<a href=\"https:\/\/stackoverflow.com\/questions\/28482809\/c-access-static-members-using-null-pointer\" target=\"_blank\" rel=\"noreferrer noopener\">somewhat unclear<\/a>&nbsp;whether it is permissible to access static members via a strongly-typed null pointer, so this alternative seems somewhat risky:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">        \/\/ dereferencing null pointer to access static member - unclear legality\n        static_for_each_base([&amp;](auto* base) { total += base-&gt;list_price(); });\n<\/pre>\n\n\n\n<p>C++20 adds the ability to name the deduced template types of a lambda, so this becomes slightly less awkward:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">        static_for_each_base([&amp;]&lt;typename Base&gt;(Base*) { total += Base::list_price(); });\n<\/pre>\n\n\n\n<p>You might want the static and nonstatic versions of&nbsp;<code>for_each_base<\/code>&nbsp;to agree on the type of the parameter passed to the visitor, in which case you can have the nonstatic version also pass a pointer:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typename... bases&gt;\nstruct Aggregator : bases...\n{\n    template&lt;typename Visitor&gt;\n    void for_each_base(Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases*&gt;(this))), ...);\n    }\n\n    template&lt;typename Visitor&gt;\n    static void static_for_each_base(Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases*&gt;(nullptr))), ...);\n    }\n};\n\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\n{\npublic:\n    int price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        for_each_base([&amp;](auto* base) { total += base-&gt;price(); });\n        return total;\n    }\n\n    static int list_price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        static_for_each_base([&amp;](auto* base) {\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\n            total += Base::list_price();\n        });\n        return total;\n    }\n};\n<\/pre>\n\n\n\n<p>This aligns the two versions, but it may also make it easier to mistakenly move code from the non-static version to static version without realizing that the meaning of the pointer has changed. I\u2019ll let you decide which is better.<\/p>\n\n\n\n<p>A final consolidation could be merging the instance and static versions by taking an explicit starting point for the aggregator, either null or non-null.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">template&lt;typename... bases&gt;\nstruct Aggregator : bases...\n{\n    template&lt;typename Visitor&gt;\n    static void for_each_base(Aggregator* self, Visitor&amp;&amp; visitor)\n    {\n        (void(visitor(static_cast&lt;bases*&gt;(self))), ...);\n    }\n};\n\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\n{\npublic:\n    int price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        for_each_base(this, [&amp;](auto* base) { total += base-&gt;price(); });\n        return total;\n    }\n\n    static int list_price()\n    {\n        int total = 10; \/* extra 10 for packaging *\/\n        \/\/ C++20: [&amp;]&lt;typename Base&gt;(Base*) {\n        for_each_base(nullptr, [&amp;](auto* base) {\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\n            total += Base::list_price();\n        });\n        return total;\n    }\n};\n<\/pre>\n\n\n\n<p>\u00b9 Though fans of&nbsp;<a rel=\"noreferrer noopener\" href=\"https:\/\/www.youtube.com\/watch?v=nmCUXNQlWto\" target=\"_blank\">a Swedish children\u2019s television show from 2004<\/a>&nbsp;may remember an episode that involved such a contraption, with the obvious name&nbsp;<em>kudderadio<\/em>. (Sorry, I couldn\u2019t find a link to the&nbsp;<em>kudderadio<\/em>&nbsp;episode.)<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<ul class=\"wp-block-list\" id=\"block-7a83d597-f530-46f5-8e8d-a59d093fddf7\"><li><em>Published at: <\/em><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210114-00\/?p=104714\">https:\/\/devblogs.microsoft.com\/oldnewthing\/20210114-00\/?p=104714<\/a><\/li><li><em>Author: <\/em><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/author\/oldnewthing\">Raymond Chen<\/a><\/li><li><em>Publication date: <\/em>January 14th, 2021<\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Here is an interesting article showing how you can use modern C++ features (C++17 and C++20) for automating the call of functions shared by base classes. Tested on Compiler Explorer&#8230; <a class=\"read-more-link\" href=\"https:\/\/tbekk.com\/devstream\/blog\/2021\/01\/26\/write-a-c-class-that-iterates-over-its-base-classes\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,88],"tags":[77,85,86,89],"class_list":["post-429","post","type-post","status-publish","format-standard","hentry","category-development","category-oop","tag-c-3","tag-c17","tag-c20","tag-fold-expressions"],"_links":{"self":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/429","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/comments?post=429"}],"version-history":[{"count":6,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/429\/revisions"}],"predecessor-version":[{"id":440,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/429\/revisions\/440"}],"wp:attachment":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/media?parent=429"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/categories?post=429"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/tags?post=429"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}