
Can we have C#-style explicit outparams in C++? elcapaldo showed me a neat approach last week that I'll try to reproduce here. (Of course Logan supplied the cool stuff; any errors are all mine.)
How it looks in C#
In C#, everything defaults to pass-by-value. If you want to be able to modify an argument to a method, you have to attach either a ref
or out
modifier to the argument at both the method definition and at each call site:
// 'value' is marked as an outparam public bool TryGetValue(string key, out string value) { ... } public void SomeMethod() { string value; if (TryGetValue("bar", out value)) // 'value' must be marked as an outparam { ... } }
The out
keyword implies a number of things, but the most noticeable effect is that consumers of methods with outparams have to explicitly acknowledge that they're using outparams.
Can we do something similar in C++?
How it looks in C++
Consider the following definition of the Out
type, along with a couple of helper functions named out
:
// parameters of type T can be passed as type Out<T> template <typename T> class Out { private: struct OutRefHolder { T &ref_; explicit OutRefHolder(T &ref) : ref_(ref) {} }; public: Out(OutRefHolder &&oref) : ref_(oref.ref_) {} Out(Out &&o) : ref_(o.ref_) {} Out(const Out&) = delete; Out() = delete; template <typename Q> void operator=(Q &&q) { ref_ = std::forward<Q>(q); } template <typename Y> friend typename Out<Y>::OutRefHolder out(Y &ref); template <typename Y> friend typename Out<Y>::OutRefHolder out(Out<Y> &o); private: T &ref_; }; // helper functions to create something convertible to an Out<Y> template <typename Y> typename Out<Y>::OutRefHolder out(Y &ref) { return typename Out<Y>::OutRefHolder(ref); } template <typename Y> typename Out<Y>::OutRefHolder out(Out<Y> &o) { return typename Out<Y>::OutRefHolder(o.ref_); }
Given that definition, we can use the Out
type and the out
functions like so:
#include <iostream> #include <string> #include "Out.h" bool get_prop(const std::string &key, Out<std::string> value) { if (key == "keymaster") { value = "gatekeeper"; return true; } return false; } int main() { std::string value; if (get_prop("keymaster", out(value))) { std::cout << value << '\n'; // displays "gatekeeper" } }
The definition of Out
makes it hard to mis-use: an Out<T>
isn't copyable and isn't easy to create, except indirectly via the out
functions.
The result: if you write functions that take an Out<T>
, the easiest path by far forces people to explicitly acknowledge that they're passing an outparam. Just like C#.