Skip to content
wxWidgets - Cross-Platform GUI Library
Stop Russia agression of Ukraine
Stop the War in Ukraine

wxWidgets is united with the people of Ukraine and the international community.

Operators Are Your Friends

Posted on

Or, at least, they should be. But for most of wxWidgets history the operators defined in the library itself, for its own types, were not very friendly. For example, if you worked on your program containing the following

#include <wx/wx.h>

class MyClassWithALotOfData {
    // TODO: Implement the "a lot of data" part.
};

int main() {
    MyClassWithALotOfData first, second;
    return first == second;
}

and tried to compile it with gcc, you would get the following errors (it’s ok if your eyes glaze over when you look at them):

oper.cpp: In function ‘int main()’:
oper.cpp:9:18: error: no match for ‘operator==’ (operand types are ‘MyClassWithALotOfData’ and ‘MyClassWithALotOfData’)
    9 |     return first == second;
      |            ~~~~~ ^~ ~~~~~~
      |            |        |
      |            |        MyClassWithALotOfData
      |            MyClassWithALotOfData
In file included from /usr/local/include/wx/dialog.h:16,
                 from /usr/local/include/wx/wx.h:64,
                 from oper.cpp:1:
/usr/local/include/wx/sharedptr.h:158:6: note: candidate: ‘template<class T, class U> bool operator==(const wxSharedPtr<T>&, const wxSharedPtr<U>&)’
  158 | bool operator == (wxSharedPtr<T> const &a, wxSharedPtr<U> const &b )
      |      ^~~~~~~~
/usr/local/include/wx/sharedptr.h:158:6: note:   template argument deduction/substitution failed:
oper.cpp:9:21: note:   ‘MyClassWithALotOfData’ is not derived from ‘const wxSharedPtr<T>’
    9 |     return first == second;
      |                     ^~~~~~
In file included from /usr/local/include/wx/wx.h:14:
/usr/local/include/wx/defs.h:924:17: note: candidate: ‘bool operator==(char, const wxUniChar&)’
  924 |     inline bool operator op(T1 x, T2 y) { return y oprev x; }
      |                 ^~~~~~~~
...
... many lines snipped
...
... and when I say many, I really mean it
...
In file included from /usr/local/include/wx/dc.h:24,
                 from /usr/local/include/wx/wx.h:51:
/usr/local/include/wx/brush.h:109:13: note: candidate: ‘bool operator==(wxBrushStyle, wxDeprecatedGUIConstants)’
  109 | inline bool operator==(wxBrushStyle s, wxDeprecatedGUIConstants t)
      |             ^~~~~~~~
/usr/local/include/wx/brush.h:109:37: note:   no known conversion for argument 1 from ‘MyClassWithALotOfData’ to ‘wxBrushStyle’
  109 | inline bool operator==(wxBrushStyle s, wxDeprecatedGUIConstants t)
      |                        ~~~~~~~~~~~~~^```

with most of them snipped because the complete output contains 526 lines, with just the first 6 of them being actually relevant and the rest being uselessly confusing and ensuring that you don’t even see the first lines with the actual description of the error as they would scroll out of your terminal or IDE window.

This was due to the fact that whenever the compiler encountered an operator (any operator, not necessarily just ==) that it couldn’t match, it had to try using all of the operators defined for wxWidgets types, such as wxSharedPtr<> or wxBrushStyle appearing above, but also many, many others, and diligently report that it couldn’t use any of them. The compiler didn’t really have any choice because this is what the language rules dictate: all the operators defined in the global scope have to be considered in this case.

However there is a better way, known as “hidden friends” idiom and popularized (but probably not invented, although I’d welcome any information about who did invent this) by Anthony Williams in his blog post. So if we define these operators as friends of e.g. wxSharedPtr directly in this class declaration, they would still work just fine when used with wxSharedPtr, as intended, but wouldn’t be considered for anything else. And this is exactly what the latest wxWidgets version does, for all these operators, so that now compiling the program above results in just

oper.cpp: In function ‘int main()’:                                            
oper.cpp:9:18: error: no match for ‘operator==’ (operand types are ‘MyClassWithALotOfData’ and ‘MyClassWithALotOfData’)
    9 |     return first == second;                                                                                    
      |            ~~~~~ ^~ ~~~~~~                                                                                     
      |            |        |                                                                                          
      |            |        MyClassWithALotOfData                                                                      
      |            MyClassWithALotOfData                                                                               

and nothing else, making the error immediately clear and obvious.

Moreover, even if there are no errors, there are still benefits to the new approach: compiler has to consider fewer candidates and so compilation process is faster, too.

Of course, as always, there are some caveats. The main, and most important one, is that this change is not fully backwards-compatible: if you previously had some type implicitly convertible to e.g. wxString, you could compare it with wxString or anything string-like via this implicit conversion and operator==() defined by wxWidgets. This is not the case any longer and while this is not always a problem – and could, indeed, fix unwanted comparisons and other operators being available for your type – it may be, in some cases, and you will have to convert the objects to wxString explicitly or define your own operator now.

The second one is that Microsoft C++ compiler in its infinite wisdom still considers even the hidden friend operators when looking for overload candidates. This helpful behaviour can be disabled by using /permissive- compiler flag which is done implicitly if you use /std:c++20 with this compiler (which is recommended if possible, of course).

But globally this should be a big improvement and wxWidgets-defined operators shouldn’t get in your way when using operators with your own types ever again.

Comments

Blog Archive