{"id":111134,"date":"2025-05-01T07:00:00","date_gmt":"2025-05-01T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111134"},"modified":"2025-05-01T09:33:54","modified_gmt":"2025-05-01T16:33:54","slug":"20250501-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250501-00\/?p=111134","title":{"rendered":"Using C++ type aliasing to avoid the ODR problem with conditional compilation, part 1"},"content":{"rendered":"<p>Some time ago, <a title=\"The difference between undefined behavior and ill-formed C++ programs\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240802-00\/?p=110091\"> I discussed the C++ concept of <i>ill-formed no diagnostic required<\/i> (IFNDR)<\/a>. A common accidental source of this is violating the <i>One Definition Rule<\/i> (ODR) by defining a class or function differently based on compile-time switches.<\/p>\n<pre>\/\/ widget.h\r\n\r\nstruct Widget\r\n{\r\n    Widget();\r\n\r\n    void SetName(std::string const&amp; name);\r\n\r\n    \u27e6 more stuff \u27e7\r\n\r\n#ifdef EXTRA_WIDGET_DEBUGGING\r\n    Logger m_logger;\r\n\r\n    void Log(std::string const&amp; message) {\r\n        m_logger.log(message);\r\n    }\r\n#else\r\n    void Log(std::string const&amp; message) {\r\n        \/\/ no extra logging\r\n    }\r\n#endif\r\n\r\n    std::string m_name;\r\n};\r\n<\/pre>\n<p>If one .cpp file is compiled with extra widget debugging enabled, but another is compiled with extra widget debugging disabled, and they are linked together, then you have a One Definition Rule violation because the <code>Widget<\/code> structure and the <code>Widget::Log<\/code> method have conflicting definitions.<\/p>\n<p>But all is not lost.<\/p>\n<p>Type aliases are not subject to the One Definition Rule!<\/p>\n<p>It&#8217;s okay to have a type alias with different definitions in different translation units because a type alias is just a a way to introduce an alternate name for an existing type; it does not introduce a new type.<\/p>\n<pre>\/\/ widget.h\r\n\r\ntemplate&lt;bool debug&gt;\r\nstruct WidgetT\r\n{\r\n    WidgetT();\r\n\r\n    \u27e6 more stuff \u27e7\r\n\r\n    [[<a title=\"MSVC C++20 and the \/std:c++20 Switch\" href=\"https:\/\/devblogs.microsoft.com\/cppblog\/msvc-cpp20-and-the-std-cpp20-switch\/\">msvc::no_unique_address<\/a>]]\r\n    [[no_unique_address]]\r\n    std::conditional_t&lt;debug, Logger, <a title=\"What's the point of std::monostate? You can't do anything with it!\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240708-00\/?p=109959\">std::monostate<\/a>&gt; m_logger;\r\n\r\n    void Log(std::string const&amp; message) {\r\n        if constexpr (debug) {\r\n            m_logger.log(message);\r\n        }\r\n    }\r\n\r\n    std::string m_name;\r\n};\r\n\r\nextern template struct WidgetT&lt;false&gt;;\r\nextern template struct WidgetT&lt;true&gt;;\r\n\r\n#ifdef EXTRA_WIDGET_DEBUGGING\r\nusing Widget = WidgetT&lt;true&gt;;\r\n#else\r\nusing Widget = WidgetT&lt;false&gt;;\r\n#endif\r\n\r\n\/\/ widget.cpp\r\n#include \"widget.h\"\r\n\r\n<span style=\"border: solid 1px currentcolor;\">template&lt;bool debug&gt;<\/span>\r\nvoid Widget<span style=\"border: solid 1px currentcolor;\">&lt;debug&gt;<\/span>::Widget()\r\n{\r\n    \u27e6 constructor stuff \u27e7\r\n}\r\n\r\n<span style=\"border: solid 1px currentcolor;\">template&lt;bool debug&gt;<\/span>\r\nvoid Widget<span style=\"border: solid 1px currentcolor;\">&lt;debug&gt;<\/span>::SetName(std::string const&amp; name)\r\n{\r\n    m_name = name;\r\n    Log(\"Name changed\");\r\n    Log(name);\r\n}\r\n\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">template struct WidgetT&lt;false&gt;;<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">template struct WidgetT&lt;true&gt;; <\/span>\r\n<\/pre>\n<p>There are two versions of <code>WidgetT<\/code>. The <code>WidgetT&lt;true&gt;<\/code> is the debugging version, and the <code>WidgetT&lt;false&gt;<\/code> is the non-debugging version. In the debugging version, there is a <code>Logger<\/code> member object, and in the non-debugging version, we have a <code>std::monostate<\/code>, which is a dummy object that does nothing. We also mark the object as <code>no_<wbr \/>unique_<wbr \/>address<\/code> to tell the compiler that it&#8217;s okay to collapse the empty object to nothing, so that it disappears entirely when not debugging.\u00b9<\/p>\n<p>All of the methods are implemented as templates, which is a bit annoying, but it&#8217;s just a bunch of boilerplate repetition for each method you want to implement.<\/p>\n<p>Since the implementations are not in the header file, we have to instantiate the templates explicitly to trigger the code generation. There are two versions of the template, and we instantiate them both.<\/p>\n<p>Meanwhile, when clients use the <tt>widget.h<\/tt> header file, they can pick what they want the name <code>Widget<\/code> to refer to. For example, if they have debugging enabled, then it is an alias for <code>WidgetT&lt;true&gt;<\/code>, so when they create a <code>Widget<\/code>, they are creating a <code>WidgetT&lt;true&gt;<\/code>, and when they call a method on it, they are calling a method of <code>WidgetT&lt;true&gt;<\/code>, and when they pass it to another function, they are passing a <code>WidgetT&lt;true&gt;<\/code>.<\/p>\n<p>Meanwhile, another client has debugging disabled, then all of its operations on a <code>Widget<\/code> are really happening with a <code>WidgetT&lt;true&gt;<\/code>. Even though each client uses the name <code>Widget<\/code> to refer to a different thing, there is no conflict here because the compiler cares about the actual type, not any nickname you may have given it.<\/p>\n<pre>\/\/ client1.h\r\n\r\n#include &lt;widget.h&gt;\r\n\r\nvoid Client1DoSomething(Widget const&amp; widget);\r\n<\/pre>\n<p>If Client1 is compiled with debugging enabled, its <tt>client1.cpp<\/tt> will implement <code>void Client1DoSomething(<wbr \/>Widget&lt;true&gt; const&amp; widget)<\/code>. But if Client2 is compiled with debugging disabled, its <tt>client2.cpp<\/tt> will try to call <code>void Client1DoSomething(<wbr \/>Widget&lt;false&gt; const&amp; widget)<\/code>. Since there is no definition for that function, you get a linker error.<\/p>\n<p>If the two clients (which disagree on what <code>Widget<\/code> refers to) try to talk to each other through a <code>Widget<\/code>, you will get a linker error because one side is trying to call a function that takes a <code>WidgetT&lt;true&gt;<\/code>, but the other side implemented a function that takes a <code>WidgetT&lt;false&gt;<\/code>.<\/p>\n<p>There is a tricky bit if one or the other client exposes a class that uses a <code>Widget<\/code>.<\/p>\n<pre>\/\/ client1.h\r\n\r\n#include &lt;widget.h&gt;\r\n\r\nstruct Client1\r\n{\r\n    Widget m_widget;\r\n};\r\n<\/pre>\n<p>Then Client1 (with debugging enabled) thinks that the <code>Client1<\/code> structure uses a <code>Widget&lt;true&gt;<\/code>, but Client2 (with debugging disabled) thinks that the the <code>Client1<\/code> structure uses a <code>Widget&lt;false&gt;<\/code>. This is an ODR violation, and depending on how unlucky you are, it may go undetected.<\/p>\n<p>Similarly, there is an ODR violation with the global function if the presence of the <code>Widget<\/code> is in something that doesn&#8217;t participate in overload resolution, like a return value.<\/p>\n<pre>\/\/ client1.h\r\n\r\n#include &lt;widget.h&gt;\r\n\r\nWidget Client1MakeWidget();\r\n<\/pre>\n<p>Okay, so I got stuck.<\/p>\n<p>But I think can still save this. We&#8217;ll do that next time.<\/p>\n<p><b>Bonus reading<\/b>: <a title=\"What is __wchar_t (with the leading double underscores) and why am I getting errors about it?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20161201-00\/?p=94836\"> What is <code>__wchar_t<\/code> (with the leading double underscores) and why am I getting errors about it<\/a>? uses a similar technique to deal with multiple possible definitions of <code>wchar_t<\/code>.<\/p>\n<p>\u00b9 The <code>no_<wbr \/>unique_<wbr \/>address<\/code> attribute also tells the compiler that if there is any trail padding in the <code>Logger<\/code> object, it is allowed to put other <code>WidgetT<\/code> members inside the trail padding.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Still one definition, but two types.<\/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-111134","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Still one definition, but two types.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111134","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=111134"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111134\/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=111134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}