Operator overloading.
Looks great. Reduces verbosity.
Until it doesn’t.
Consider this overloaded function call operator:
struct StorageLoader { template<typename DataType> DataType operator()(StorageOptions<DataType> const* options); };
The idea is that you can use the function call operator on a StorageLoader
object to load data from storage, using a StorageOptions
to describe how you want it to be loaded.
StorageOptions<Data1> data1Options; data1Options.ignore_missing(true); StorageLoader storageLoader; Data1 data1 = storageLoader(&data1Options);
The parameter is accepted as a pointer so you can pass nullptr
to indicate that you accept all defaults.
// Oops, this doesn't work. Data1 data1 = storageLoader(nullptr);
The nullptr
doesn’t work because the compiler can’t read your mind to figure out which overload you’re trying to call. You have to help it along, either by refining the type of the parameter:
Data1 data1 = storageLoader(static_cast<StorageOptions<Data1>*>(nullptr));
or by explicitly specializing the function call operator.
Data1 data1 = storageLoader.operator()<Data1>(nullptr);
Neither of these is very attractive, and they certainly defeat any conciseness benefit of an overloaded operator.
I personally am a fan of giving named function equivalents to overloaded operators, particularly if they are templated. In this case, I would have done something like
struct StorageLoader { template<typename DataType> DataType Load(StorageOptions<DataType> const* options); template<typename DataType> DataType operator()(StorageOptions<DataType> const* options) { return Load(options); } };
The function call operator is just a convenient shorthand for calling the Load
method.
// Using function call operator data1 = storageLoader(&data1Options); // Using named method data1 = storageLoader.Load(&data1Options); // Named method works better for nullptr data1 = storageLoader.Load<Data1>(nullptr);
And then I can make the parameter to Load
default to nullptr
:
struct StorageLoader
{
template<typename DataType>
DataType Load(StorageOptions<DataType> const* options
= nullptr);
...
};
which allows you to write
// If no parameters, then use default options data1 = storageLoader.Load<Data1>();
Sometimes, the meaning of an overloaded operator is unclear, in which case having an explicit name also helps avoid confusion over what it does. (I’m looking at you, overloaded address-of operator.)
Also, giving a name to the overloaded operator makes generating a pointer-to-method a little less awkward.
“The parameter is accepted as a pointer so you can pass nullptr to indicate that you accept all defaults.” seems like a non-ideal API design that also leads to the described problem.
An example of a slightly better API, I think, would require an explicit StorageOptions constant to indicate default options. It will also solve the problem with the operator().
Why not modify the StorageLoader a bit:
Then your “accept all defaults” function call would look even nicer:
Cheers!
You cannot deduce the return type. What would
auto data = storageLoader()
use for the DataType?Math teacher: “I want to teach you guys a new technique called: Multiplication.”
Johnny: “But we can do that using Addition.”
Math teacher: “Yes, but I am trying to teach you Multiplication”
Johnny: “Addition !!!”
Math teacher: “Multiplication is just another way of doing things, with the added advantage that it can provide clarity.”
Johnny: “Addition !!!”
Math teacher: *sigh*
Rust solves this one straightforwardly: each overloadable operator has a corresponding trait that is normal aside from the compiler knowing about the correspondence. To overload the operator, you implement the trait.
And then there’a a general syntax for referring to any function, so you can get a pointer to it, or call it, that way (this breaks a bit for anonymous types, but is easily worked around).
Another great article. 👍
I have one question about an old article:
https://devblogs.microsoft.com/oldnewthing/20140908-00/?p=53
Here you start notepad and put the text into it.
Since Notepad is a store app and has tabs, this doesn't work anymore.
WaitForInputIdle will wait forever. The process only has 0 for MainWindowHandle as the exe is only a wrapper to start the store app and notepad is only a single process with many windows and tabs and the process I start...
jeez does it take over 100ms for notepad to start up these days? this is the power of modern apps.
Store apps are always slow to start.
The whole concept of the store apps is not very good.
It’s like a foreign body in Windows.