Your question gives a confused impression to me, because I am not sure which actual problem you are trying to solve.
You showed us
void foo(int i)
// version 1.1
void foo(int i, bool enablethis=true)
Assumed the behaviour of
foo(x,true) is the same as the behaviour of version 1.0 of
foo(x), there no issue, the code behaves backwards compatible and no caller has to be changed.
Then you added
Version 1.2 forces me to change the function to
void foo(int i, std::string whatever, bool enablethis=true)
Taking this literally, this could break backwards compatibility, because any caller now has to provide a string parameter
whatever. If this is an issue or not depends a lot on the real context, especially how much control you have over the calling code, and what semantics the parameter
whatever introduces. Some possible cases:
You have the code which calls
foo fully under your control, and you want all places where
foo is called to add a sensible value for
whatever in a deliberate manner. Then go ahead, the compiler will to tell you where you might have forgotten to extend a call to
foo, and the signature change is not an issue, quite the opposite.
You would prefer the existing callers to be untouched, maybe because you don't have all the calling code under your control, or because changing all the calls would imply too much testing effort. Then you could change the order of arguments and provide a default value for
foo(int i, bool enablethis=true, std::string whatever="")
This will allow you not to think about the callers
foo(x,false), which can remain untouched.
Now, how does this compare to the idea of using a helper class
InputParameters? In fact, this does not gain anything in regards to backwards compatibility. If your callers have to initialize an object of type
InputParameters before calling
foo, extending it with another non-optional attribute
whatever will just shift the need for change from the call to
foo to the caller's initialization code in front of the call to
foo. And in case
whatever can be made optional by providing a default value in the constructor of
InputParameters, it does not make a huge difference to the variant #2 I scetched above. Assumed you add proper constructors to
InputParameters, the calling side would look something like this:
// instead of foo(i)
// instead of foo(i,enablethis);
// instead of foo(i,enablethis, whatever);
So when does it really make sense to bundle several input parameters of a function into a new class or data structure? A growing number of arguments can be indeed a code smell, but the real issue here is neither backwards compatibility nor scalability, but readability and maintainability. And for this, it is important not to bundle mechanically every parameter in some class with a nonsensical name like
InputParameters. That would cause the opposite of what you are trying to achieve, since whenever one now will try to call foo, they first look at the signature, see that there is some black box
InputParameters there, and have to lookup that class will require and which members it contains.
Instead, group several parameters together in one helper class if you can give them a sensible, common name, some superordinate term which tells readers what the class represents. This is also called "creating an abstraction". If you cannot come up with a sensible name for a group of parameters, because there is no logical grouping, you better leave them separated.
Of course, there may be more driving factors here: another motivation for creating a class
InputParameters (hopefully with a clearer name) could be that it makes it possible to add some code here, for example, default initialization code for some of the members which were too complex to be provided in the signature of
foo. Or, you know that the caller may want to separate calling from initialization. Or, as others have mentioned,
InputParameters is just some base class in a class hierarchy of different kinds of input parameters.
So in short, bundling input parameters of a function into a new class can make sense, but not for the reasons you probably had in mind when asking this question, and not just "in general" - it depends all on the specific context.