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.
The way I understand the referenced reddit comment:
You can manually re-implement OOP-style extensibility on top of weak functions instead of virtual functions.
Yes, that approach breaks if you access members directly, you need to go via functions.
Yes, that approach comes with a performance overhead, that might be mitigated in LTO, just like virtual functions.
Yes, you need to make sure to have a weak destructor.
You would be better of just using inheritance and virtual functions, but it is technically possible. It would be brittle and surprising and I would shoot it down in code review.
Why does SHAddToRecentDocs restrict folders to only be shown for Windows Explorer? Snippet from the function page:
Folders are also accepted by SHAddToRecentDocs, but appear only in the Jump List for the Windows Explorer taskbar button. Folders do not appear in any other application’s Jump List.
One case where this doesn't work is if you need to mix different compiler ABI modes, e.g. /await vs /await:strict. What I did was have a public header with ABI-independent definitions (e.g. polymorphic virtual base classes) and then a private header that puts ifdefs around the namespace name to make it so each ABI variant has its own inline namespace. The private header implements the public interfaces one time and they end up in different inline namespaces automatically based on the compiler settings of whatever source file included it (to avoid ODR violations), and any ABI differences get type-erased out...
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.
Ok, to solve that problem, we have to take path 1. Path 2 won't work.
We have a number of functions called CreateWidgetModuleXyz (where Xyz is the module name). CreateWidget is also weak.
All of these are weak functions where the default implementation is a jump to CreateWidget or an alias for CreateWidget if your linker is smart enough. To activate the logger for a specific module, link in its module logger library. All of the libraries also provide a COMDEF definition of CreateWidget that allocates the space for the logger pointer and zeroes the pointer to it. The logger function itself...