Here’s something crazy: You have a function that has two different callers. One of them expects the function to return a widget. The other expects the function to return a doodad.
class widget; class doodad; class experiment { public: doodad get_entity(); widget get_entity(); };
This is not allowed in C++. The return type of a function cannot be overloaded.
But Kenny Kerr taught me how to fake it. What you do is return an object that doesn’t yet know whether it’s a widget or doodad.
class experiment { public: auto get_entity() { struct result { operator widget() { return experiment->get_entity_as_widget(); } operator doodad() { return experiment->get_entity_as_doodad(); } experiment* experiment; }; return result{ this }; } };
The thing that is returned is neither a widget nor a doodad, but observing it will trigger a collapse to one or the other.
widget w = exp.get_entity(); doodad p = exp.get_entity();
In the first call, the get_
entity()
returns the private result
type, and then immediately assigns it to a variable of type widget
. This triggers the operator widget()
conversion operator, which calls get_
entity_
as_
widget
.
Similarly, the second call obtains the private result
type and converts it to a doodad
, which winds up calling get_
entity_
as_
doodad
.
The wave function collapse could be triggered by anything that accepts a conversion.
move_widget(exp.get_entity()); // will call get_entity_as_widget signal_doodad(exp.get_entity()); // will call get_entity_as_doodad
If you take the return value of get_
entity
and save it in an auto
, then the wave function hasn’t collapsed yet. It’s still not sure which thing it is.
auto entity = exp.get_entity();
The thing doesn’t become a widget or doodad until you convert it.
move_widget(entity); // calls get_entity_as_widget
Note that the call to get_
entity_
as_
widget
is delayed until the conversion occurs.
auto entity = exp.get_entity(); exp.replace_entity(); move_widget(entity); // calls get_entity_as_widget
Between calling get_
entity
and converting the result to a widget, we changed the entity in the experiment. Not until the conversion occurs does the call to get_
entity_
as_
widget
happen, at which point it will get the new entity rather than the original one. And of course, if you destroy the experiment, then the unresolved entity
has a dangling pointer, and the behavior is undefined.
This trick works best if the caller will convert the result immediately to its final type (widget or doodad).
Of course, you could try to fix these problems, say by taking a strong reference to the experiment
to prevent it from being destructed prematurely. Or you could call both get_
entity_
as_
widget
and get_
entity_
as_
doodad
as part of the constructor, and then hand out the appropriate type during the conversion. That would fix the “delayed evaluation” problem, but at a cost of doing eager evaluation of both branches, even if only one will end up being used.
In the case where Kenny used it, it was to permit the First
method to return a different iterator depending on who’s asking for it. The underlying problem is the object wants to be able to produce a stream of T
objects or IInspectable
objects, so it implements both the IIterable<T>::First()
and IIterable<IInspectable>::First()
methods. The projection for those interfaces forward to the implementation’s First()
, which forces First()
to serve two masters. And the way he solved it was to return an ambiguous object, so that each master sees what it wants.
C++17 allows you to use std::variant:
std::variant experiment::get_entity(){
//blah blah
return widget{};
//blah blah
return doodad{};
};
experiment ex;
auto res {ex.get_entity()};
if (res.holds_alternative()){
auto& w{std::get()};
//use w
};
if (res.holds_alternative()){
auto& d{std::get()};
//use d
};
Indeed, C++17 introduced std::variant. But that only solves part of the problem, namely, the inability to overload on return type only. It does not address the other part: Having the callee return a type the caller expects. Instead, this implementation subscribes to a specific type when calling get_entity(). The comments reading "blah blah" hide a problem for which there is no solution: Dispatching to the concrete type based on expectations of the caller, without any...
I asked this very question as a puzzle on stackoverflow eleven years ago (back when stackoverflow allowed this kind of stuff).
As for the problem with auto, it has also been bothering me for years, I suggested to Herb Sutter that C++ add a way for a class to configure the way it's deduced for auto. My idea was to have an operator auto (which you can = delete if you want to prevent the problem...
I believe that the technique first appeared in the D language, and it’s called Voldemort types: https://wiki.dlang.org/Voldemort_types
Very cool indeed.
From the wiki page, it seems the feature of Voldemort types is that it is anonymous, not that it can respond according to the larger expression of which it is a part.
The method demonstrated in this entry is the well-known temporary proxy idiom. It’s canonically used for simulating C# indexer in C++ operator[] — think of this as a superposition of T& and T const.
I thought there was a proposal for a C++ feature that one could use in struct result, to force auto entity = exp.get_entity(); to be eagerly converted to some other type, but I can't find it now. WG21 P0936R0 and P0849R2 are somewhat related, though.
edit: Found P0672R0 immediately after posting the comment, of course.
That’s actually a really cool trick. I didn’t realize C++ had the ability to overload casting operators (which also sounds terrifying). Now I’m curious how this could be implemented in C# or Kotlin…
The specific case in the blog entry should be resolved using explicit interface implementation in C#.
I don’t know about Kotlin but C# also supports overloading casting operators https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators
It has to, or the object has to provide a lot of .ToWhateverType() methods to simulate conversion between types.