computer science goes bonk / all posts / rss / about

explicit out-parameters

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#.