{"id":104714,"date":"2021-01-14T07:00:00","date_gmt":"2021-01-14T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104714"},"modified":"2021-01-14T07:54:31","modified_gmt":"2021-01-14T15:54:31","slug":"20210114-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210114-00\/?p=104714","title":{"rendered":"How can I write a C++ class that iterates over its base classes?"},"content":{"rendered":"<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<p>For example, say we have <code>Pillow<\/code> and <code>Radio<\/code> classes:<\/p>\n<pre>class Pillow\r\n{\r\npublic:\r\n    int price();\r\n    int weight();\r\n    void refurbish(int level);\r\n};\r\n\r\nclass Radio\r\n{\r\npublic:\r\n    int price();\r\n    int weight();\r\n    void refurbish(int level);\r\n};\r\n<\/pre>\n<p>And you want to create a <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&#8217;s go along with it.<\/p>\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<pre>class PillowRadio : public Pillow, public Radio\r\n{\r\npublic:\r\n    int price()\r\n    {\r\n        int total = 0;\r\n        for (typename T : base_classes_of(this)) {\r\n            total += T::price();\r\n        }\r\n        return total + 10; \/* extra 10 for packaging *\/\r\n    }\r\n\r\n    int weight()\r\n    {\r\n        int total = 0;\r\n        for (typename T : base_classes_of(this)) {\r\n            total += T::weight();\r\n        }\r\n        return total + 5; \/* extra 5 for packaging *\/\r\n    }\r\n\r\n    void refurbish(int level)\r\n    {\r\n        for (typename T : base_classes_of(this)) {\r\n            T::refurbish(level);\r\n        }\r\n    }\r\n};\r\n<\/pre>\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<p>So how do you do this?<\/p>\n<p>C++ doesn&#8217;t provide this degree of reflection but you can simulate it by introducing a helper class.<\/p>\n<pre>template&lt;typename... bases&gt;\r\nstruct Aggregator : bases...\r\n{\r\n    int price()\r\n    {\r\n        return (0 + ... + bases::price());\r\n    }\r\n\r\n    int weight()\r\n    {\r\n        return (0 + ... + bases::weight());\r\n    }\r\n\r\n    void refurbish(int level)\r\n    {\r\n        (bases::refurbish(level), ...);\r\n    }\r\n};\r\n\r\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\r\n{\r\npublic:\r\n    int price()\r\n    {\r\n        return Aggregator::price() + 10; \/* extra 10 for packaging *\/\r\n    }\r\n\r\n    int weight()\r\n    {\r\n        return Aggregator::weight() + 5; \/* extra 5 for packaging *\/\r\n    }\r\n\r\n    \/* inherit refurbish from Aggregator *\/\r\n};\r\n<\/pre>\n<p>How does this work?<\/p>\n<p>The <code>Aggregator<\/code> class is given a list of base classes, and it dutifully derives from them. So that solves the first problem: Deriving from an <code>Aggregator<\/code> causes you to derive from all of the specified base classes.<\/p>\n<p>The methods on <code>Aggregator<\/code> use <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/fold\"> fold expressions<\/a> which iterate over the template type parameters and combine the results in some way.<\/p>\n<p>For the case of <code>refurbish<\/code>, we don&#8217;t actually have any results to combine; we just want to invoke <code>refurbish<\/code> on each base class, so we use the comma operator to throw the results away after invoking each method. Fortunately, <code>refurbish<\/code> returns <code>void<\/code>, so we don&#8217;t have to worry about somebody doing a <a title=\"Rough edges in the when_all coroutine, part 2: Overloaded comma operator\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200904-00\/?p=104172\"> sneaky overload of the comma operator<\/a>.<\/p>\n<p>Of course, this <code>Aggregator<\/code> is tightly coupled to the methods of its base classes. Maybe we can generalize it.<\/p>\n<pre>template&lt;typename... bases&gt;\r\nstruct Aggregator : bases...\r\n{\r\n    template&lt;typename Visitor&gt;\r\n    void for_each_base(Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases&amp;&gt;(*this))), ...);\r\n    }\r\n};\r\n<\/pre>\n<p>The <code>for_each_base<\/code> method takes a visitor functor and calls it once for each base class. We cast the result to <code>void<\/code> so that we can <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200904-00\/?p=104172\"> safely use the comma fold operator<\/a> to throw the results away after each call of the visitor.<\/p>\n<p>Now we can implement the aggregator methods for our <code>PillowRadio<\/code> class.<\/p>\n<pre>class PillowRadio : Aggregator&lt;Pillow, Radio&gt;\r\n{\r\npublic:\r\n    int price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        for_each_base([&amp;](auto&amp;&amp; base) { total += base.price(); });\r\n        return total;\r\n    }\r\n\r\n    int weight()\r\n    {\r\n        int total = 5; \/* extra 5 for packaging *\/\r\n        for_each_base([&amp;](auto&amp;&amp; base) { total += base.weight(); });\r\n        return total;\r\n    }\r\n\r\n    void refurbish(int level)\r\n    {\r\n        for_each_base([&amp;](auto&amp;&amp; base) { base.refurbish(level); });\r\n    }\r\n};\r\n<\/pre>\n<p>Okay, but what about static members?<\/p>\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<pre>template&lt;typename... bases&gt;\r\nstruct Aggregator : bases...\r\n{\r\n    template&lt;typename Visitor&gt;\r\n    void for_each_base(Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases&amp;&gt;(*this))), ...);\r\n    }\r\n\r\n    <span style=\"color: blue;\">template&lt;typename Visitor&gt;\r\n    static void static_for_each_base(Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases*&gt;(nullptr))), ...);\r\n    }<\/span>\r\n};\r\n<\/pre>\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<pre>class Pillow\r\n{\r\npublic:\r\n    static int list_price();\r\n};\r\n\r\nclass Radio\r\n{\r\npublic:\r\n    static int list_price();\r\n};\r\n\r\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\r\n{\r\npublic:\r\n    static int list_price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        static_for_each_base([&amp;](auto* base) {\r\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\r\n            total += Base::list_price();\r\n        });\r\n        return total;\r\n    }\r\n};\r\n<\/pre>\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<p>It is <a href=\"https:\/\/stackoverflow.com\/questions\/28482809\/c-access-static-members-using-null-pointer\"> somewhat unclear<\/a> whether it is permissible to access static members via a strongly-typed null pointer, so this alternative seems somewhat risky:<\/p>\n<pre>        \/\/ dereferencing null pointer to access static member - unclear legality\r\n        static_for_each_base([&amp;](auto* base) { total += base-&gt;list_price(); });\r\n<\/pre>\n<p>C++20 adds the ability to name the deduced template types of a lambda, so this becomes slightly less awkward:<\/p>\n<pre>        static_for_each_base([&amp;]&lt;typename Base&gt;(Base*) { total += Base::list_price(); });\r\n<\/pre>\n<p>You might want the static and nonstatic versions of <code>for_each_base<\/code> 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<pre>template&lt;typename... bases&gt;\r\nstruct Aggregator : bases...\r\n{\r\n    template&lt;typename Visitor&gt;\r\n    void for_each_base(Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases*&gt;(this))), ...);\r\n    }\r\n\r\n    template&lt;typename Visitor&gt;\r\n    static void static_for_each_base(Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases*&gt;(nullptr))), ...);\r\n    }\r\n};\r\n\r\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\r\n{\r\npublic:\r\n    int price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        for_each_base([&amp;](auto* base) { total += base-&gt;price(); });\r\n        return total;\r\n    }\r\n\r\n    static int list_price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        static_for_each_base([&amp;](auto* base) {\r\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\r\n            total += Base::list_price();\r\n        });\r\n        return total;\r\n    }\r\n};\r\n<\/pre>\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&#8217;ll let you decide which is better.<\/p>\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<pre>template&lt;typename... bases&gt;\r\nstruct Aggregator : bases...\r\n{\r\n    template&lt;typename Visitor&gt;\r\n    static void for_each_base(Aggregator* self, Visitor&amp;&amp; visitor)\r\n    {\r\n        (void(visitor(static_cast&lt;bases*&gt;(self))), ...);\r\n    }\r\n};\r\n\r\nclass PillowRadio : Aggregator&lt;Pillow, Radio&gt;\r\n{\r\npublic:\r\n    int price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        for_each_base(this, [&amp;](auto* base) { total += base-&gt;price(); });\r\n        return total;\r\n    }\r\n\r\n    static int list_price()\r\n    {\r\n        int total = 10; \/* extra 10 for packaging *\/\r\n        \/\/ C++20: [&amp;]&lt;typename Base&gt;(Base*) {\r\n        for_each_base(nullptr, [&amp;](auto* base) {\r\n            using Base = std::decay_t&lt;decltype(*base)&gt;;\r\n            total += Base::list_price();\r\n        });\r\n        return total;\r\n    }\r\n};\r\n<\/pre>\n<p>\u00b9 Though fans of <a href=\"https:\/\/www.youtube.com\/watch?v=nmCUXNQlWto\"> a Swedish children&#8217;s television show from 2004<\/a> may remember an episode that involved such a contraption, with the obvious name <i lang=\"se\">kudderadio<\/i>. (Sorry, I couldn&#8217;t find a link to the <i lang=\"se\">kudderadio<\/i> episode.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Variadic templates to the rescue.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-104714","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Variadic templates to the rescue.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104714","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=104714"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104714\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=104714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}