If you were to look at this code,
int x = 0;
function(x);
std::cout << x << \'\\n\';
you would not be able to verify through any me
This is C++: the lack of in
and out
parameters doesn't mean the language is deficient, it means you need to implement what other languages would do as a language feature as a library.
Create two template
classes and functions.
in_param
is a wrapper around a T const&
, whilie io_param
is a wrapper around a T&
reference. You construct them by calling helper functions in
and io
.
Inside, they behave like references (via overloading).
Outside, the caller must call in
or io
on the argument, marking it up at the call site.
out
is trickier: inside the fumction, only assignment is legal. Ideally we would not even construct it: an emplace
method might help.
However, the caller needs some channel to know if the parameter was constructed or not.
What I would do is out_param
only has operator=
, and it assigns. out
wraps something into an out_param
. If you want delayed constructuon, use optional
inside the out param, which gets close. Maybe out_param
also has emplace
, which usually just assigns, but if the tyoe wrapped has emplace
calls it instead?
template
struct in_param : std::reference_wrapper {
explicit in_param( T const& t ):std::reference_wrapper(t) {}
in_param( in_param&& o ):std::reference_wrapper(std::move(o)) {}
void operator=( in_param const& o ) = delete;
};
template
struct io_param : std::reference_wrapper {
explicit io_param( T& t ):std::reference_wrapper(t) {}
io_param( io_param&& o ):std::reference_wrapper(std::move(o)) {}
};
template
in_param< T > in( T const& t ) { return in_param(t); }
template
io_param< T > io( T& t ) { return io_param(t); }
template
struct out_param {
private:
T& t;
public:
out_param( T& t_ ):t(t_) {}
out_param( out_param&& o ):t(o.t) {}
void operator=( out_param const& o ) = delete;
void operator=( out_param && o ) = delete;
void operator=( out_param & o ) = delete;
void operator=( out_param && o ) = delete;
template
out_param& operator=( U&& u ) {
t = std::forward(u);
return *this;
}
// to improve, test if `t` has an `emplace` method. If it does not,
// instead do t = T( std::forward(us)... ). (I'd use tag dispatching
// to call one of two methods)
template
void emplace( Us&&... us ) {
t.emplace( std::forward(us)... );
}
};
template
out_param out( T& t ) { return out_param(t); }
or something like the above.
You now get syntax like:
void do_stuff( int x, in_param y, io_param z, out_param d );
int main() {
expensive a;
something b;
double d;
do_stuff( 7, in(a), io(b), out(d) );
}
and failure to call in
, io
or out
at the call site results in compile time errors. Plus, out_param
makes it quite difficult to accidentally read the state of the out
variable within the function, producing some very nice documentation at the call site.