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 means of syntax, that parameter x is being passed by reference or that it's being passed by value. The only way you would know for sure, is if you looked at either the function declaration or function definition.
Here is a simple example of how I believe this could be a problem:
std::string Lowercase(std::string str); //<- this is hidden away in code; probably in a different file.
int main(){
std::string str = "HELLO";
Lowercase(str);
std::cout << str << '\n'; //<- Bug! we expected to output "hello". The problem is not very easy to spot, especially when a function name sounds as though it will change the passed in value.
}
In order to avoid having to jump between the function call and the function declaration (or in some cases, the documentation) in order to understand the function behavior, is there a way to explicitly document in the syntax of a function call that the parameter is expected to change (i.e. a reference parameter) or that a copy is being sent (i.e. pass by value)?
I realize that there is also the option of passing by const& which has the similar notion to passing by value, in that the variable passed in, will not have its value changed after the function call.
I'm sure there are all kinds of situations in the language that might add to the complexity of understanding how a parameter is being passed-
but I'm curious, is there a way to combat this problem in the way I want to?
I've noticed that some people write two similar functions. One of them takes a value parameter, the other one takes a pointer. That allows calling a function like this:
Lowercase(str); //we assume the value will not change
Lowercase(&str); //we assume the value will change
But this solution has many other issues, and I would not like to lose the benefit of references. Plus, we are still making assumptions on the behavior.
Some people insist that the correct way to pass mutable object is to use a pointer. That is, you would pass
Lowercase(&str);
... and Lowercase()
would, obviously, be implemented to take a pointer. That approach may suit your needs.
I want to mention, however, that this is not what I would do! Instead, the approach I favor is to use appropriate names instead. For example,
inplace_lowercase(str);
pretty much says what it is going to do. Clearly, inplace_lowercase()
would actually be an algorithm and with a bit of magic could be reasonably be called as
inplace_lowercase(str.begin() + 1, str.end());
as well.
Here are a few reasons why I don't like passing arguments by pointer and/or why I don't believe in an explicit indication of how the argument is passed:
- Pointers can be null. A mandated reference parameters should, in my opinion, be a reference.
- Passing by pointer still doesn't indicate whether the argument may be modified are not as the argument may be a
T const*
.
- Having meaningful names makes it actually easier to understand what's going on in the first place.
- Calling something without consulting its documentation and/or knowing what the called function will do doesn't work anyway and indicating how things are passed is trying to cure symptoms of a deeper problem.
I'm not sure I understand your requirements completely, but maybe this is something you can use:
template<typename T>
void foo( T ) { static_assert( sizeof(T)==0, "foo() requires a std::ref" ); }
void foo( std::reference_wrapper<int> t )
{
// modify i here via t.get() or other means of std::reference_wrapper
}
int main()
{
int i = 42;
// foo( i ); // does not compile, static_assert fires
foo( std::ref( i ) ); // explicit std::ref visible on the caller's side
}
Many (most) IDE's help you with this problem by displaying the function/method prototype(s) once they figure out which function you are calling.
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<T>
is a wrapper around a T const&
, whilie io_param<T>
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<typename T>
struct in_param : std::reference_wrapper<T const> {
explicit in_param( T const& t ):std::reference_wrapper<T const>(t) {}
in_param( in_param<T>&& o ):std::reference_wrapper<T const>(std::move(o)) {}
void operator=( in_param<T> const& o ) = delete;
};
template<typename T>
struct io_param : std::reference_wrapper<T> {
explicit io_param( T& t ):std::reference_wrapper<T>(t) {}
io_param( io_param<T>&& o ):std::reference_wrapper<T>(std::move(o)) {}
};
template<typename T>
in_param< T > in( T const& t ) { return in_param<T>(t); }
template<typename T>
io_param< T > io( T& t ) { return io_param<T>(t); }
template<typename T>
struct out_param {
private:
T& t;
public:
out_param( T& t_ ):t(t_) {}
out_param( out_param<T>&& o ):t(o.t) {}
void operator=( out_param<T> const& o ) = delete;
void operator=( out_param<T> && o ) = delete;
void operator=( out_param<T> & o ) = delete;
void operator=( out_param<T> && o ) = delete;
template<typename U>
out_param<T>& operator=( U&& u ) {
t = std::forward<U>(u);
return *this;
}
// to improve, test if `t` has an `emplace` method. If it does not,
// instead do t = T( std::forward<Us>(us)... ). (I'd use tag dispatching
// to call one of two methods)
template<typename... Us>
void emplace( Us&&... us ) {
t.emplace( std::forward<Us>(us)... );
}
};
template<typename T>
out_param<T> out( T& t ) { return out_param<T>(t); }
or something like the above.
You now get syntax like:
void do_stuff( int x, in_param<expensive> y, io_param<something> z, out_param<double> 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.
If you use MS VC++ then maybe it will be useful information about source-code annotation language (SAL)
http://msdn.microsoft.com/ru-ru/library/hh916383.aspx
I think it's something useless to notify (by language nonetheless [1]). The only needed question is : "Is my object is semantically modified ?", and so :
- When you read a prototype you know if a function could modify an object (non-const ref) or not (copy or const ref).
- When you use a function (even without reading [2] the prototype) if you have to be sure to not modify an object, use a const_cast.
[1] A static analyzer could do it for its purposes.
[2] If you miss, the compiler would warn you anyway.
That is the whole point of pass-by-reference -- that syntactically don't need to do anything different from passing by value.