Choosing Your Function Arguments

Choosing Your Function Arguments

23 Jul 2018    

A function name and it’s arguments should be enough to let a user know what the function does without browsing the function’s source code. While the name of the function plays a big part in that, the function arguments are the main communication tool that can be used to convey information to a user. There are 3 main messages that a function argument may convey to the user, not all of them are valid for template arguments due to reference collapsing rules:

1. Usage

If you see a function argument that is generally either const or const &, you can safely assume that the function will only be using that argument and not modifying it’s internal state.

void FetchData(Request const& request,
                    Network_callback const* callback); // still not const

Another type of usage is when you have to access something owned by that type which isn’t const i.e. call a non-const function. The best way to convey this would be to pass by pointer Type* const indicating a non-const usage.

An exception to these are resource views like std::string_view or std::function_ref or gsl::span which although are arguments that are taken by copy wrap a cheap non-owning abstraction.

2. Copy

When trying to convey that an argument is going to be copied there are a couple of ways of doing this, with caveats.

void SetData(std::vector<int> rows); // by value, instant copy
void SetName(std::string const& name); // by ref, internal copy

If the type is (cheaply) movable then the best thing to do is to use the function argument as a non const value type and move it into the final destination within the function body. This has a caveat that the copy operation can throw when calling the function. If in the rare instance that the type is not movable then resort to using const &.

3. In/Out Parameters

Lastly, if you want to populate bunch of data that you require the user to setup, then use a plain reference as the function argument Type&. This not only makes it very clear that the argument is going to be modified but also means that the user is required to make sure it’s valid, compared to using a pointer argument, since a reference is required to be valid while a pointer may not.

It is preferable to just return the parameter instead since passing one by reference could indicate it’s either in and out or just out which is difficult to assess without doing some more reading.

Additionally, for example if you’re taking a struct by const & and only using two of it’s 15 member variables then please, take those two member variables as separate arguments. Unless you are using functions belonging to a certain type or using most of it’s member variables then there is no good reason to use the struct as a whole.

Lastly, the best way to write a function is to create a Pure Function. A function that doesn’t modify any of it’s arguments, doesn’t rely on any previous state and always produces the same output given the same inputs. Pure functions are very readable and easy to comprehend and reduce potential bugs.