November 21st, 2025
0 reactions

Maybe somebody can explain to me how weak references solve the ODR problem

Some time ago, I proposed Using type aliasing to avoid the ODR problem with conditional compilation. A Reddit thread claimed that “It’s weak functions basically. You can actually achieve this without needing templates but need to separate the weak and non-weak definitions.”

I’m not seeing how weak functions help here, but maybe somebody can set me straight.

Weak functions are still functions. If you are changing data layout (as we are in our case of adding a Logger member), you can weaken any functions that access the data members, but that doesn’t help any class consumers that access those data members, because the data member accesses are inlined into the call site.

Let’s look at our Widget again.

// 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;
};

If somebody just accesses the name directly:

void sample(Widget& w)
{
    std::cout << w.m_name;
}

The access to w.m_name is inlined into the sample function. It’ll probably go something like

    mov     rsi, edi                ; rsi -> Widget
#ifdef EXTRA_WIDGET_DEBUGGING
    add     rsi, 16                 ; offset of m_name
                                    ; if there is a Logger
#else
    add     rsi, 24                 ; offset of m_name
                                    ; if there isn't a Logger
#endif
    mov     rdi, offset std::cout
    call    operator<<        ; print the name to cout

The compiler is going to inline the offset to m_name, so it will choose an offset based on whether the EXTRA_WIDGET_DEBUGGING symbol is set. But if the caller is building with EXTRA_WIDGET_DEBUGGING set the opposite way, then you are printing garbage.

The idea behind templating the Widget based on whether it is debugging or not is to allow parts of the code to use a debugging Widget, but other parts to use a non-debugging Widget.

template<bool debugging>
struct WidgetT
{
    ⟦ ... ⟧
};

#ifdef EXTRA_WIDGET_DEBUGGING
using Widget = WidgetT<true>;
#else
using Widget = WidgetT<false>;
#endif

The pieces that use a debugging widget get WidgetT<true>, and those that don’t get a WidgetT<false>.

While weak pointers let you provide two versions of a function (say, debugging and non-debugging), you can’t have both versions active at the same time. Everybody has to agree on whether it’s a debugging version or a non-debugging version.

But maybe I’m missing something. If you can explain how to accomplish this with weak functions, let me know in the comments.

Bonus chatter: Another downside of weak functions is that they aren’t inlined, but maybe link time optimization can re-inline them.

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.

2 comments

Sort by :
  • Joshua Hudson

    I’m seeing it.

    In order for it to work, one of the following must be true:

    1) The logger is at the end of the class *and* all calls to the constructor are inside a replaceable method. Logically, this is the equivalent of a derived class. Clients never see a widget, they see a widget * or a widget & instead.

    2) The ifdef doesn’t cover the declaration of Logger, but only its initialization.

    • Raymond ChenMicrosoft employee Author

      Okay, but it does mean that everybody in the project is either using logging widgets or non-logging widgets. You can’t have some files using logging (say because you’re trying to debug a problem there), while other files don’t use logging.