3

Let's say you call a function like this:

someFunc( some_int, some_float, false, "whatever text");

This doesn't look good but if alternatively I pass these by a struct / class, it won't look much better, since I will make up the struct on-the-fly like I do with function parameters.

someFunc( FuncParameters( some_int, some_float, false, "whatever text"));

Even if the struct has the parameter names in it's definition, I don't see those when I call it's constructor.

I can do this instead:

FuncParameters func_parameters;
func_parameters.some_int_data = some_int;
func_parameters.some_float_data = some_float;
func_parameters.some_text_data = "whatever text";

someFunc(func_parameters);

But if I forget the bool-data like above, then nothing will complain about it.

So why do people say that "always pass parameters in structure if there are more than X number of parameters? What am I missing?

Newline
  • 769
  • 3
  • 12
  • 1
    You can add a member to the struct without breaking old code. – Biffen Nov 14 '19 at 14:36
  • 1
    maybe you are looking for the builder pattern. I personally think it is a great way to write lots of boiler plate for little gain and prefer your last solution (+ fixing the "nothing will complain" part) – 463035818_is_not_an_ai Nov 14 '19 at 14:36
  • 1
    I'm voting to close as primarily opinion-based. The two approaches are isomorphic. Some people prefer one way for some reasons, and others prefer the other way. Oftentimes something in between works. – AndyG Nov 14 '19 at 14:37
  • "So why do people say ... ?" is strictly speaking not a quesiton about code but about peoples opinion. I think you could edit it to make the question focus on code rather than opinions – 463035818_is_not_an_ai Nov 14 '19 at 14:38

2 Answers2

4

Using structures instead of separate parameters has several advantages:

  • You express that data is related. By naming the struct appropriately, you can give a name to this relation.
  • A struct grouping related data has a chance to be reused elsewhere in your code.
  • Once you modify the struct (add/remove members), you don't need to adapt parameter lists. This is particularly true if you have calls through multiple functions, where the struct object is simply passed through.
  • Named members at the call site (soldier.speed = 3.2;) are clearer than simply passing 3.2. This applies even more to bool literals and function calls such as CreateSoldier(3.2, true, false, true).

There are also some things to keep in mind:

  • How you pass the struct (by value or pointer) has an effect on how much data is copied, and if you have an extra dereference at runtime.
  • For simple code, creating a struct only to pass it to a function can actually increase the verbosity and reduce readability of your code.
  • Only in C99 you have compound literals. For older versions of C it's not possible to pass temporary objects, and you need to explicitly declare a variable.
  • As you noticed, a well designed function can enforce initialization of all parameters; but you don't see the parameter's name at call site. For functions not taking many parameters, this can be mitigated by naming the function appropriately (e.g. SetSpeed(&soldier, 3.2)).
TheOperator
  • 5,936
  • 29
  • 42
2

I think one main reason is code readability. You can read in CppCoreGuidelines

Keep the number of function arguments low

Reason Having many arguments opens opportunities for confusion. Passing lots of arguments is often costly compared to alternatives.

...

Grouping arguments into "bundles" is a general technique to reduce the number of arguments and to increase the opportunities for checking.

Example

void f(int* some_ints, int some_ints_length);  // BAD: C style, unsafe

versus

void f(gsl::span<int> some_ints);              // GOOD: safe, bounds-checked

With your example

someFunc(10,20,true,"foo");

is more confusing than:

struct SomeFuncParameters
{
  int max_iterations = 100;
  float epsilon = 1.e-6;
  bool reinit = true;
  std::string name = "";
};

void someFunc(const SomeFuncParameters& parameters) {}

int main()
{
  // Variation 1
  //
  someFunc(SomeFuncParameters());

  // Variation 2
  //
  someFunc(SomeFuncParameters{.max_iterations = 10, 
                              .epsilon = 1e-4, 
                              .reinit = false, 
                              .name = "my name"});

  // Variation 3
  //
  SomeFuncParameters someFuncParameters;
  someFuncParameters.epsilon = 1e-10;

  someFunc(someFuncParameters);
}

even if you have to write a longer code.

Also note that when you use a struct to store parameters you can easily define parameter default values. You also have less code to modify if you want to add or remove some parameters as the call sites like:

someFunc(SomeFuncParameters());
someFunc(someFuncParameters);

won't be affected.


I also wrote a small C++17 lib for named optional arguments that can maybe interest you.

Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70