A customer wanted to add XAML support to their application, so they included (among other things) the header file winrt/Windows.UI.Xaml.h. But the mere act of including that header file produced a horrible series of errors that began with.
winrt\impl\Windows.UI.Xaml.0.h(4323,28): warning C4003: not enough arguments for function-like macro invocation 'ErrorMessage' winrt\impl\Windows.UI.Xaml.0.h(4323,28): error C2146: syntax error: missing ')' before identifier 'ErrorMessageString' winrt\impl\Windows.UI.Xaml.0.h(4323,28): error C3646: 'ErrorMessageString': unknown override specifier winrt\impl\Windows.UI.Xaml.0.h(4323,28): error C2059: syntax error: ')' winrt\impl\Windows.UI.Xaml.0.h(4323,48): error C2238: unexpected token(s) preceding ';'
What’s going on?
The errors are coming from the following line of code:
template <typename D>
struct consume_Windows_UI_Xaml_IExceptionRoutedEventArgs
{
[[nodiscard]] auto ErrorMessage() const;
};
That line of code looks like a perfectly fine method declaration. Why is the compiler upset?
Look closely at the first error message.
not enough arguments for function-like macro invocation 'ErrorMessage'
Wait a second, “function-like macro invocation”? This says that the compiler thinks you’re trying to use a macro called ErrorMessage
.¹
We suspected that the developer had a macro named ErrorMessage
elsewhere in their project, and that was conflicting with the method name. And with some help from the customer, we found it.
// Produces the error text for an HRESULT. struct ErrorMessageString { ErrorMessageString(HRESULT hr); operator PCWSTR() { return (PCWSTR)m_errorMessage; } CStringW m_errorMessage; }; #define ErrorMessage(hr) ((PCWSTR)ErrorMessageString(hr))
Bonus reading: Compiler error message metaprogramming: Helping to find the conflicting macro definition
The ErrorMessage
macro creates an ErrorÂMessageÂString
object whose constructor looks up the error message string associated with the provided HRESULT
, and then forces it to produce a string pointer. Evidently, the intended purpose is to use it something like this:
LogMessage(L"Problem toggling the widget", ErrorMessage(hr));
Macros have no respect for boundaries. The C++/WinRT header file is trying to declare a method named ErrorÂMessage()
, and the macro steps in and says “I’ll take care of that!” It then sees that the code provides no parameters, but the macro requires one parameter, so you get the “not enough arguments” error. And then the compiler shrugs its shoulders and says, “Eh, I’ll just substitute it anyway,” and you end up with
template <typename D>
struct consume_Windows_UI_Xaml_IExceptionRoutedEventArgs
{
[[nodiscard]] auto ((PCWSTR)ErrorMessageString()) const;
};
This is nonsense, and the cascade errors are a bunch of error messages telling you how nonsensical this is.
One solution is to remove the macro when including the troublesome header file.
#pragma push_macro("ErrorMessage") #undef ErrorMessage #pragma push_macro("GetCurrentTime") #undef GetCurrentTime #pragma push_macro("TRY") #undef TRY #include <winrt/Windows.UI.Xaml.h> ⟦ other C++/WinRT headers ... ⟧ #pragma pop_macro("TRY") #pragma pop_macro("GetCurrentTime") #pragma pop_macro("ErrorMessage")
I pre-emptively added GetÂCurrentÂTime
and TRY
because you’re going to run into that problem really soon, so we may as well take care of it now.
Bonus reading: The Turkish lira’s currency code is an unexpected source of problems with computer programmers.
A better solution is simply to get rid of the macro.
// Don't use this. It returns a dangling pointer.
inline PCWSTR ErrorMessage(HRESULT hr) { return (PCWSTR)ErrorMessageString(hr); }
¹ A “function-like” macro is one that is defined with a parenthesized parameter list, possibly empty.
Last sample has copy-paste issue.
#undef ErrorMessage second time should be fixed to
#undef GetCurrentTime
From the look of it
Doesn’t the non-macro version have a use-after-free?
Indeed it does. Rats.
Fixed version:
By putting the ErrorMessageString object in the default argument it manifests as a temporary and its lifetime is extended to the full expression in the caller’s context (similarly to the macro). The && is necessary, otherwise function parameters are not temporaries and have some implementation-defined lifetime rules: they are either destroyed at the end of the function or at the end of the full expression in the caller’s context.