Function arguments vs. function parameters in C/C++


Posted by Diego Assencio on 2016.11.22 under Programming (C/C++)

A function is a set of instructions which encapsulate a certain task. Usually, a function receives input data from the caller as a sequence of arguments and processes them internally through parameters (variables) which are initialized by their corresponding arguments. Function parameters appear in function definitions, and function arguments appear in function calls. For example, consider the pseudocode below:

ReturnType some_function(ParameterType1 p1, ..., ParameterTypeN pN)
{
	...
}

...

some_function(expr1, ..., exprN);

In order to call some_function, we need $N$ arguments; in the code above, these arguments are the results of evaluating the expressions expr1, ..., exprN. The parameters of some_function are the (local) function variables p1, ..., pN with types ParameterType1, ..., ParameterTypeN respectively. When some_function is called, the $n$-th argument it receives is used to initialize its $n$-th parameter.

While it seems as if we are merely giving names to things here, the distinction between a function parameter and a function argument is very important, especially in C++. This fact comes from the fact that, when a function is called:

1.the types of its parameters need not in general be the same as the types of their associated arguments
2.user-defined types have their own constructors and therefore a parameter which has a user-defined type can be initialized in possibly more than one way

The first point can be illustrated with the simple example below:

/* do_something has a single parameter n of type unsigned int */
int do_something(unsigned int n)
{
	...
}

...

/* call do_something with the (int) value 3 as argument */
do_something(3);

In the code above, the argument value 3 which is passed to do_something has type int and is used to initialize the function parameter n which has type unsigned int (this initialization involves an implicit type conversion of the value 3 from int to unsigned int). Inside the scope of do_something, n is a normal variable which holds the value 3 as an unsigned int.

The second point above is the really interesting one. Consider this example:

#include <string>
#include <iostream>

class Message
{
public:
	Message(int n)
	{
		std::cout << "Message::Message(int)\n";
	}

	Message(const std::string& m)
	{
		std::cout << "Message::Message(const std::string&)\n";
	}

	Message(std::string&& m)
	{
		std::cout << "Message::Message(std::string&&)\n";
	}
};

void log_message(Message msg)
{
	/* log the input Message */
}

int main()
{
	std::string str = "Hello, World!";

	log_message(3);
	log_message(str);
	log_message(std::move(str));

	return 0;
}

The output of this program may surprise the reader:

Message::Message(int)
Message::Message(const std::string&)
Message::Message(std::string&&)

In this example, the fact that msg is initialized by the corresponding argument passed to log_message has a very visible result. On the first call to log_message, the argument 3 has type int, so msg is initialized through the constructor Message::Message(int). On the second call to log_message, the argument str is an lvalue of type std::string, so msg is initialized through the constructor Message::Message(const std::string&). Finally, on the third call to log_message, std::move(str) is an rvalue of type std::string&& and therefore msg is initialized through the constructor Message::Message(std::string&&). In other words, the choice of which constructor to use to initialize the function parameter msg depends directly on the type of argument passed to log_message even though the type of msg itself is always the same: Message.

To sum up, a function parameter is a regular variable which is initialized by the corresponding input argument sent when the function it belongs to is called, and the type of a parameter needs not be the same as the type of the argument used to initialize it.

Comments

No comments posted yet.