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_ symbol is set. But if the caller is building with EXTRA_ 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.
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.
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.