May 2nd, 2025

Using type aliasing to avoid the ODR problem with conditional compilation, part 2

Last time, we tried using type aliasing to avoid the ODR problem with conditional compilation, but we ran into trouble with cases where a change in type does not participate in overload resolution.

But we can still salvage the situation.

We can follow the same basic pattern, but instead of allowing the debug and non-debug versions to coexist, we can simply prohibit them from coexisting.

First, let’s isolate the two versions into separate translation units.

// widgetimpl.h
#include "widget.h"

template<bool debug>
void Widget<debug>::Widget()
{
    ⟦ constructor stuff ⟧
}

template<bool debug>
void Widget<debug>::SetName(std::string const& name)
{
    m_name = name;
    Log("Name changed");
    Log(name);
}

// widgetdebug.cpp
#include "widgetimpl.h"

template struct WidgetT<true>;

// widgetnondebug.cpp
#include "widgetimpl.h"

template struct WidgetT<false>;

Now we can use the One Definition Rule to our advantage: Declare the same variable in each translation unit.

// widgetdebug.cpp
#include "widgetimpl.h"

template struct WidgetT<true>;

namespace Widgets::internal
{
    extern const bool is_debug = true;
}

// widgetnondebug.cpp
#include "widgetimpl.h"

template struct WidgetT<false>;

namespace Widgets::internal
{
    extern const bool is_debug = false;
}

If somebody uses a debugging-mode Widget, then that pulls in the explicit instantiation from widgetdebug.obj, and that in turn pulls in the debugging definition of Widgets::internal::is_debug. And if somebody else uses a non-debugging Widget, then that will pull in the explicit instantiation from widgetnondebug.obj, which in turn pulls in the non-debugging definition of Widgets::internal::is_debug.

Two definitions for the same thing is a diagnosable One Definition Rule violation, and the linker will complain.

Phew, we did it.

Now, if you are using the Microsoft Visual C++ compiler, you could have used #pragma detect_mismatch from the outset and avoided this hassle.

// widget.h

struct Widget
{
    Widget();

    void SetName(std::string const& name);

    ⟦ more stuff ⟧

#ifdef EXTRA_WIDGET_DEBUGGING
    Logger m_logger;

    void Log(std::string const& message) {
        m_logger.log(message);
    }
#else
    void Log(std::string const& message) {
        // no extra logging
    }
#endif

    std::string m_name;
};

#ifdef EXTRA_WIDGET_DEBUGGING
#pragma detect_mismatch("widget build mode", "debug")
#else
#pragma detect_mismatch("widget build mode", "nondebug")
#endif

If two clients set EXTRA_WIDGET_DEBUGGING differently, the linker will complain about the mismatch.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Motti Lanzkron

    Could you put the

    #pragma detect_mismatch

    inline in the class definition rather than repeat the pre-processor flag?

  • Ivan Kljajic

    Would name decoration for every combination of #if’s help? So a link would fail. Or would that just be annoying because some inlines or funcs would be fine?

  • Joshua Hudson

    My old C++ textbook gave a very simple method.

    “`
    class WidgetImpl;
    struct Widget {
    Widget();

    void SetName(std::string const& name);
    void Log(std::string const& message)
    WidgetImpl *impl;
    };
    “`

    The only copy of WidgetImpl is in the implementation of Widget.

    Avoiding the extra heap allocation is indeed possible but there’s no one-size-fits-all method of doing it.

    • 許恩嘉

      I think the cost of heap allocation will be higher than simply keeping the debug variable.