Simulation of Keyword (Named) Arguments in C++ and C
Bringing a bit of Python readability to C++/C
By Prabhu Ullagaddi (Linked-in) , 2024-11-20
Introduction
In a programming language,function keyword arguments (aka named parameters) have following benefits (compared to the positional arguments of functions)
Improved readability of code
Eases code development
Avoids a class of bugs without human effort
Zero cost abstraction (aka Zero runtime overhead) (proof needed)
Provides backward compatible extensibility of your functions
Human friendly because we no more need to remember positions of args. At the same time we are very good in remembering tuples like (Name:Value) in everyday life!
Many more!
Python provides first class support for keyword arguments. There is an excellent article on Python keyword argument in the Post by Trey Hunner. C++ (or C) does not provide the same. However, we can achieve the same with a simple trick. This trick will be a Zero Cost Abstraction, hence we don’t need to worry about runtime cost due to this in C++/C yet we gain all benefits of keyword args.
Let us see how to simulate this in C++/C. Then we will discuss how it has some added advantages. If you need more justification for keyword arguments, this post [Why you shouldn't default to positional parameters] may be helpful. Please let me be a little non-technical before showing actual code. For those who used C/C++ but not Python/JavaScript/HTML/gRPC may not find the keyword args a great way to improve code. My view on s/w is: we develop code not just for machines to execute but also for fellow humans to read and maintain it. We must make every effort to make our code readable to our fellow people. Keyword arg is one such tool in our kit! Let us use it!. Or ask this : Why are the creators of languages like Python provided the first class support of keyword args instead of just living with positional args (as in C/C++)? What is that drove them to go beyond C/C++ in this respect?.
Simulating Keyword arguments in C++
This is achieved with designated initializers of a class/struct. Let me explain with an example. (Please note, I use pass by value method, but it can be used with pass by reference or any other method that a programming lang provides. )
Assume, you want to write a function that takes age and name of a person but accepts something similar to keyword args of Python.
/* to compile this program use c++20 std as below.
bash$ g++ -std=c++20 1.cpp && ./a.out
if you want to use older c++ std, then you need to explicitly type cast the argument in function call to the type as declared in function declaration.
This would be similar to the C version I mentioned in later part of this article.
*/
/* simulated keyword arguments in C++/C */
#include <iostream>
using namespace std;
class Person {
public:
std::string name;
int age;
int salary = 100;
};
void function(const Person& person) {
cout << "Details of the person" << endl;
cout << " name: " << person.name << endl;
cout << " age: "<< person.age << endl;
cout << "salary: "<< person.salary << endl;
return;
}
int main(){
function({.name="Alex", .age=18}); // salary is with default value of 100
function({.name="Bob", .age=21, .salary=200}); // salary is overridden
}
Output on
p@p:~$ gcc -std=c20 1.c && ./a.out
Details of the person
name: Alex
age: 18
salary: 0
Details of the person
name: Bob
age: 21
salary: 200
p@:~$
Here, in function call, compiler automatically creates a temporary object of type as defined in function declaration. Hence explicit creation of a object of type Person is not needed. However, if you like to create one, there will be no issue but it will be needlessly verbose.
Note: if you see the variable “int salary = 100”, you may notice that this method also supports the default arguments facility of C++. Again, order does not matter here. ie, this method does not rob us from any advantage that is available to us in normal way. Please let me know if I am missing something here.
Simulating Keyword arguments in C
Since C too provides the designated initializes facility, we can use same. There is a little difference. C compilers do not create a temporary objects. Hence, we need to explicitly type cast (static type cast) during function call. Let us see this.
/* to compile this program.
bash$ gcc 1.c && ./a.out
*/
/* simulated keyword arguments in C */
#include <stdio.h>
typedef struct Person {
char name[100];
int age;
int salary;
} Person;
void function(const struct Person person) {
printf("Details of the person\n");
printf(" name: %s\n", person.name);
printf(" age: %d\n", person.age);
printf("salary: %d\n", person.salary);
return;
}
int main(){
function((Person){.name="Alex", .age=18}); //salary := default val of 100
function((Person){.name="Bob", .age=21, .salary=200}); // salary is
overridden
/*in another way, we can define a new var of type Person, assign values explicitly and pass. That is also a neat but it is verbose */
}
Zero cost abstraction
Does this cost us some additional run time? I did not have time to investigate. However I am confident that this will have no extra runtime cost. Compilers are smart enough optimize the code. Interested persons can investigate this using dis-assembled code. For the best results, use the standard techniques such as
Data Padding: In struct / POD class declaration, put the variable that demands strictest alignment at the top and least alignment at the bottom and follow the gradient [padding]. This is a well known trick used in every struct/POD Class. Hence the same applies for simulation of keyword args too. One good thing is: During function call, we can mention the member variables in any order that seems natural to you. Compiler will take care of re-arranging in the order as you defined in struct/POD class declaration. By this, we understand, code is natural to read for human without sacrificing efficiency.
Additional benefits of this approach
Statically typed languages are popular and efficient since they have ability to eliminate certain class of bugs during compilation. Here, (aggregated) argument of a function itself is a type. So, this strongly binds function to its single argument. This way of aggregating function argument(s) into a class or struct is good and efficient mental model. i.e., we can think that every function takes only one argument and go on designing the software. Irrespective of, we agree or not to this, we can go and use simulated keyword argument as much as possible in our next project!
Overall, if you are convinced about the techniques explained here, you will start using them in other languages (Python/JavaScript/Rust …) too. This technique can be a general guideline for defining a function. Also, if you are willing, you can extend this to a little further - The return values of a function can be a aggregate type and tightly binding to the function. Means, a triplet : { function name, input aggregate, output aggregate} acts a set. Input and output can be passed/returned by value or reference.
Irrespective of programming language you use, this concept of triplet will help you in following way:
Yet one more way to use type safety
Actively encourages us to use functional programming techniques partially or fully in OOP.
many more!
Conclusion
Code maintenance is a cost. We must use or explore ways of developing code that is readable in short and long term. Simulated Keyword arg is one such thing until C++ committee supports this.