
C ++ named parameters. Not useful
From time to time, the named parameters in C ++ suddenly start to want. Not so long ago there was an article , and he wrote on this subject some time ago . And this is surprising - since the time of my article, I have been participating in a new project without having to drag the old code along with me, and somehow in an amazing way I haven’t used all of this. Those. figured out the issue, admired the prospects ... and continued to work the old fashioned way! How so? Laziness? Inertia? I will try to give an answer under the cut.
To get started, consider an example - declaring a function that returns a date object for a given day, month, and year.
The problem is obvious - what order of parameters do not choose, after a month, having seen this
you will guess: “What is this? The second of March 2004, or the fourth of 2002? ” If you are particularly lucky, and the team is international, the interpretation of the function by the developers can be fundamentally different. The same types are in a row in the parameter list ... In such cases, you usually want named parameters, which, in C ++, alas, are not.
Many programmers have to switch from one programming language to another. At the same time, something in the new language is like, something is not ... The bad is forgotten over time, but the good one would certainly be transferred to the environment where you are working now. There, in the same Objective-C named parameters are available!
Yes, the first parameter goes without a name, but its name is often included in the name of the method. The solution, of course, is not ideal (for example, the colorWithBlue method does not exist, such a color injustice), but the named parameters are supported at the language level. VBA is still better - names can be given to all parameters; thanks to them, you can not produce many similar methods, but get by with one. Look, for example, at Document.PrintOut
But in C ++ there is nothing like that! I immediately want to correct the situation, look for libraries, come up with crutches and bicycles. And even something will be found and succeeded. But instead, you might think, if everything is so fine, why the named parameters have not been added. So many paradigms are supported, but here it is ...
Or maybe added? Just called differently. For example, custom types. It's time to give the main idea of the article. Primitive standard types have no place in the interfaces of a real system . Such types are just building blocks from which you need to build, and not try to live inside.
For example, an object of type int is a signed integer lying in a certain range. What describes this type? Only its implementation. It cannot be, for example, the number of apples. Because apples cannot be minus 10. Still worse: unsigned intalso unsuitable for this task. Because apples in general have nothing to do with the dimensionality of the data type on your platform. By tying primitive types of language to the parameters of the open methods of our models, we make a mistake, which we then try to “hush up” with the help of various crutches. Moreover, in our desire to hide the oversight, we often ignore the simple fact that the language is trying to help us, saying: "I do not directly support this, and for good reason ...".
But the main drawback of primitive types is that the compiler loses the chance to reveal a logical error. For example, we have a method that takes two parameters - first and last name. If we reduce them to standard string types, then the compiler will see only two pieces of text, from the permutation of which the meaning will not change. As a result, one developer will pass the first parameter to the name, and the other to the last name. And both will technically be right. An error is destroyed at the beginning if special types exist for the name and surname. In a real system, where the name and surname are so significant entities that enter the interface separately, simply reducing them to strings is an error. A name is not an arbitrary string at all. If only because it is selected from a predetermined set. It also, say, does not contain numbers (although here I am not sure).
But back to the dates. A day is by no means unsigned int , not unsigned char, not even std :: string . Day is ... day! If we are building a model in which dates are present, then it makes sense to create a special type to represent the days. Custom data types are just what gives C ++ all its power and expressiveness. Then crutches become unnecessary. Introducing a class to represent days
Something like that. Naturally, for the physical representation of the value in memory, we still use the primitive type. But this is no longer part of the interface. Immediately we get full control over the contents of this field, eliminating a number of possible errors. Yes, without knowing the full date, exact restrictions cannot be established, but at least a check for falling into the interval 1..31 can already be arranged. The most important thing: to implement a special data types for months and years with the explicit ( explicit ) constructor to initialize primitive types, we get the named parameters are supported directly by the language. The function can now be called as follows
No roundabouts, no additional libraries. Yes, such an approach will not allow changing the parameters, but this is not so important. The main mission of the named parameters - to eliminate errors when calling functions and improve code readability - is carried out.
As a bonus, we get increased flexibility if, for example, we wanted to set the month also in string form. Now you don’t have to do the overloaded version of createDate (this is the function of creating a date object, what does it really care about the month format). Instead, another explicit constructor for the month type is simply added.
Everyone is now doing their own thing - createDate creates a date, and the Month class interprets and monitors the correctness of the month value.
I would like to object right away - but wouldn’t there be too many unnecessary types if we would do our own wrapper type for each primitive type? Here's how to look. If you are a student who needs to quickly write a lab, pass it and forget it, then yes - a lot of extra code and wasted time. But if we are talking about a real system, in the long and happy life of which you are interested, then I would not be superfluous to name custom types for entities used in the interface.
But what about custom types? What, for example, to do if a method accepts several objects of the same type
It all depends on the context. If all objects are equivalent, then there is no problem - nothing changes from the order of their transfer. To emphasize this, you can only transfer objects packed in an array or other container. If the objects are not equivalent, for example, the method submits user2 to the object user1, then special types that reflect the roles of objects will be quite useful. It should be wrappers around user objects (as is the case with primitive types) or it is easier to create special classes that inherit from User, depending on the system being implemented. It is important to somehow express the various roles of user1 and user2 by the means of the language, allowing the compiler to catch errors related to their possible confusion.
Which conclusion can be drawn. There is no need to strive to grab the very best of all languages and put it in one already suffering C ++. It is important to be able to overcome inertia when changing a programming language. Let's say yes, in Lua you can assign values to several variables
x at once , y = getPosition ()
The idea itself is beautiful, but whether it is needed in C ++. Generally not needed. It's easier to create a Position type and assign a value to its object. Language is a tool, nothing more. And from the fact that the tools are sometimes similar, it does not at all follow that you need to use them the same way down to the last detail.
To get started, consider an example - declaring a function that returns a date object for a given day, month, and year.
Date createDate(int day, int year, int month);
The problem is obvious - what order of parameters do not choose, after a month, having seen this
Date theDate = createDate(2, 3, 4);
you will guess: “What is this? The second of March 2004, or the fourth of 2002? ” If you are particularly lucky, and the team is international, the interpretation of the function by the developers can be fundamentally different. The same types are in a row in the parameter list ... In such cases, you usually want named parameters, which, in C ++, alas, are not.
Many programmers have to switch from one programming language to another. At the same time, something in the new language is like, something is not ... The bad is forgotten over time, but the good one would certainly be transferred to the environment where you are working now. There, in the same Objective-C named parameters are available!
+ (UIColor *)colorWithRed:(CGFloat)red
green:(CGFloat)green
blue:(CGFloat)blue
alpha:(CGFloat)alpha
Yes, the first parameter goes without a name, but its name is often included in the name of the method. The solution, of course, is not ideal (for example, the colorWithBlue method does not exist, such a color injustice), but the named parameters are supported at the language level. VBA is still better - names can be given to all parameters; thanks to them, you can not produce many similar methods, but get by with one. Look, for example, at Document.PrintOut
But in C ++ there is nothing like that! I immediately want to correct the situation, look for libraries, come up with crutches and bicycles. And even something will be found and succeeded. But instead, you might think, if everything is so fine, why the named parameters have not been added. So many paradigms are supported, but here it is ...
Or maybe added? Just called differently. For example, custom types. It's time to give the main idea of the article. Primitive standard types have no place in the interfaces of a real system . Such types are just building blocks from which you need to build, and not try to live inside.
For example, an object of type int is a signed integer lying in a certain range. What describes this type? Only its implementation. It cannot be, for example, the number of apples. Because apples cannot be minus 10. Still worse: unsigned intalso unsuitable for this task. Because apples in general have nothing to do with the dimensionality of the data type on your platform. By tying primitive types of language to the parameters of the open methods of our models, we make a mistake, which we then try to “hush up” with the help of various crutches. Moreover, in our desire to hide the oversight, we often ignore the simple fact that the language is trying to help us, saying: "I do not directly support this, and for good reason ...".
But the main drawback of primitive types is that the compiler loses the chance to reveal a logical error. For example, we have a method that takes two parameters - first and last name. If we reduce them to standard string types, then the compiler will see only two pieces of text, from the permutation of which the meaning will not change. As a result, one developer will pass the first parameter to the name, and the other to the last name. And both will technically be right. An error is destroyed at the beginning if special types exist for the name and surname. In a real system, where the name and surname are so significant entities that enter the interface separately, simply reducing them to strings is an error. A name is not an arbitrary string at all. If only because it is selected from a predetermined set. It also, say, does not contain numbers (although here I am not sure).
But back to the dates. A day is by no means unsigned int , not unsigned char, not even std :: string . Day is ... day! If we are building a model in which dates are present, then it makes sense to create a special type to represent the days. Custom data types are just what gives C ++ all its power and expressiveness. Then crutches become unnecessary. Introducing a class to represent days
class Day
{
explicit Day (unsigned char day);
//...
private:
unsigned char mValue;
};
Something like that. Naturally, for the physical representation of the value in memory, we still use the primitive type. But this is no longer part of the interface. Immediately we get full control over the contents of this field, eliminating a number of possible errors. Yes, without knowing the full date, exact restrictions cannot be established, but at least a check for falling into the interval 1..31 can already be arranged. The most important thing: to implement a special data types for months and years with the explicit ( explicit ) constructor to initialize primitive types, we get the named parameters are supported directly by the language. The function can now be called as follows
Date theDate = createDate(Day(2), Month(3), Year(4));
No roundabouts, no additional libraries. Yes, such an approach will not allow changing the parameters, but this is not so important. The main mission of the named parameters - to eliminate errors when calling functions and improve code readability - is carried out.
As a bonus, we get increased flexibility if, for example, we wanted to set the month also in string form. Now you don’t have to do the overloaded version of createDate (this is the function of creating a date object, what does it really care about the month format). Instead, another explicit constructor for the month type is simply added.
class Month
{
explicit Month(unsigned char month);
explicit Month(std::string month);
//...
private:
unsigned char mValue;
};
Everyone is now doing their own thing - createDate creates a date, and the Month class interprets and monitors the correctness of the month value.
Date theDate = createDate(Day(2), Month("Jan"), Year(4));
I would like to object right away - but wouldn’t there be too many unnecessary types if we would do our own wrapper type for each primitive type? Here's how to look. If you are a student who needs to quickly write a lab, pass it and forget it, then yes - a lot of extra code and wasted time. But if we are talking about a real system, in the long and happy life of which you are interested, then I would not be superfluous to name custom types for entities used in the interface.
But what about custom types? What, for example, to do if a method accepts several objects of the same type
User user1, user2;
//...
someMethod(user1, user2);
It all depends on the context. If all objects are equivalent, then there is no problem - nothing changes from the order of their transfer. To emphasize this, you can only transfer objects packed in an array or other container. If the objects are not equivalent, for example, the method submits user2 to the object user1, then special types that reflect the roles of objects will be quite useful. It should be wrappers around user objects (as is the case with primitive types) or it is easier to create special classes that inherit from User, depending on the system being implemented. It is important to somehow express the various roles of user1 and user2 by the means of the language, allowing the compiler to catch errors related to their possible confusion.
Which conclusion can be drawn. There is no need to strive to grab the very best of all languages and put it in one already suffering C ++. It is important to be able to overcome inertia when changing a programming language. Let's say yes, in Lua you can assign values to several variables
x at once , y = getPosition ()
The idea itself is beautiful, but whether it is needed in C ++. Generally not needed. It's easier to create a Position type and assign a value to its object. Language is a tool, nothing more. And from the fact that the tools are sometimes similar, it does not at all follow that you need to use them the same way down to the last detail.