Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Placing function parameters in the middle of the function's name

Tags:

c++

c

Can we somehow put the parameters inside the function's name, not at the end of it?

For example:

uint16_t Add(uint8_t A)To(uint8_t B)ThenMultiplyWith(uint8_t C) {...}

When called:

Result = Add(12)To(8)ThenMultiplyWith(5);

Is there any workaround using the preprocessor?

like image 842
AlMoutaz Billah Avatar asked Feb 02 '26 16:02

AlMoutaz Billah


2 Answers

Objective C/C++ does something similar to this; a message to an object looks like:

[Object method:arg1 with:arg2 and:arg3]

In C++, you can use designated initializers to get a similar looking call syntax:

auto result = Calculate( {.Add=7 .To=7 .ThenMultiplyWith=111} )

or even:

auto result = Add(7)({.To= 3, .ThenMultiplyBy=2})

or:

auto result = Add{ 7, .To=5, .ThenMultiplyBy=11 }();

Yet another technique (as mentioned) could be named operators:

auto result = Add(7) *To* 1 *ThenMultiplyBy* 2;

or, chained methods:

auto result = Add(7).To(18).ThenMultiplyBy(3).Compute();

All of this is syntactic sugar at the point of invocation. None of them look like this at the point where you define the operations.

Modifying the core grammar of the language is a problem, because the language is already relatively complex grammar wise and making it more complex is not a great plan. Every time a grammar change is added, lots of effort and brainpower is put into making sure it doesn't break anything and that the effort is worth the cost.

There have been a number of efforts to add named parameters to function calls. What we have at this point is designated initializers in structs, which is a big step, and gets you many of the benefits.

like image 127
Yakk - Adam Nevraumont Avatar answered Feb 05 '26 06:02

Yakk - Adam Nevraumont


I will presume the question isn’t about integers, even though it was phrased as such.
I will also presume to answer in C, even though the question is tagged as both C++ and C. (Answers for C++ are easy.)

Correct, Idiomatic C for Single Function

Assuming a single function, name it and use it properly. Usage is straightforward and readable to anyone who knows C:

result = AddXToYThenMultiplyWithZ(12, 8, 5);

Correct, Idomatic C for Chaining Functions

Operators, of course, have their own ordering (operand, function, operand), but this is no difficulty for functions, where the ordering is (function, operand, operand).

result = multiply(add(12, 8), 5);

We read that just as easily as:

result = ((12 + 8) * 5);

A side note about reality

Please don’t do any of the following in real code. If you try then code review should absolutely fail you.

Macro Abuse v1

Yes, you can abuse macros to do this. But there are caveats you won’t like.

#define Add(x) AddToThenMultiplyWith(x,
#define To(y) y,
#define ThenMultiplyWith(z) z)

long AddToThenMultiplyWith(long a, long b, long c)
{
  return (a + b) * c;
}

#include <stdio.h>

int main()
{
  printf("%ld\n", Add(12)To(8)ThenMultiplyWith(5) );
  return 0;
}

The main problem with this solution is that “Add” is now a synonym for this one function, and it does not play nicely unless you specify the whole weird chain correctly. Errors will give your users weird and inexplicable error messages for which they will develop an intense dislike for you and your code.

If we wish to get past that first limitation to be able to do something like a AddToThenDivideBy() kind of function as well, then you need to break the code into smaller pieces and rewrite ordering constraints.

Function Chaining with Reordering

Our goal here is to take:

result = multiply(add(a,b), c);

and rewrite it as:

result = (a add b) multiply c

In other words, we want to be able to perform some kind of method chaining that looks like C++ or currying.

Alas, C provides no re-entrant way to do that. We need to do it manually, via a helper function. Boo?

Yes, boo. It is worse than you think:

#include <stdarg.h>

long add(long a, long b) {return a + b;}
long multiply(long a, long b) {return a * b;}

long apply(long a, ...)
{
  va_list args;
  va_start(args, a);
  for (;;)
  {
    long (*f)(long, long) = va_arg(args, long(*)(long, long));
    if (!f) break;
    
    long b = va_arg(args, long);
    
    a = f(a, b);
  }
  va_end(args);
  return a;
}
#define apply(...) apply_(__VA_ARGS__)
#define apply_(...) apply(__VA_ARGS__, NULL,NULL)


#include <stdio.h>

int main()
{
  printf("%ld\n", multiply(add(12, 8), 5) );         // The correct, idiomatic way
  printf("%ld\n", apply(12, add, 8, multiply, 5) );  // Our insane way
  return 0;
}

At this point we are no longer able to use functions that have signatures varying from a single type (long(*)(long,long)).

And we are now subject to failures the compiler cannot catch, such as segfaults and coredumps. Not to mention baffling error messages should the compiler catch anything that will make your users absolutely abhor you.

But, getting past that, we can now apply our Evil™ knowledge to create macros of doom:

Macro Abuse v2

Let us add a divide() function and combine the two previous examples to create a new monstrosity:

#include <stdarg.h>

long add(long a, long b) {return a + b;}
long multiply(long a, long b) {return a * b;}
long divide(long a, long b) {return a / b;}

long apply(long a, ...)
{
  va_list args;
  va_start(args, a);
  for (;;)
  {
    long (*f)(long, long) = va_arg(args, long(*)(long, long));
    if (!f) break;
    
    long b = va_arg(args, long);
    
    a = f(a, b);
  }
  va_end(args);
  return a;
}
#define apply(...) apply_(__VA_ARGS__)
#define apply_(...) apply(__VA_ARGS__, NULL,NULL)


#define Add(x)              apply(x,add,
#define To(y)               y,
#define ThenMultiplyWith(z) multiply,z)
#define ThenDivideBy(z)     divide,z)
#include <stdio.h>

int main()
{
  printf("%lu\n", Add(12)To(8)ThenMultiplyWith(5) );
  printf("%lu\n", Add(12)To(8)ThenDivideBy(5) );
  return 0;
}

You should see that these are the same as:

  printf("%lu\n", apply(12,add,8,multiply,5) );
  printf("%lu\n", apply(12,add,8,divide,5) );

Which are the same as:

  printf("%lu\n", multiply(add(12, 8), 5) );
  printf("%lu\n", divide(add(12, 8), 5) );

Arguably, the untangled solution is significantly cleaner, more readable and easier to maintain, and less likely to fail in any aspect than either the “apply” or “apply-with-macro” solutions.

A note about <stdint.h> types

These things really only exist for storage considerations. IDK if you should be using them for general calculations.

For example, the magic apply() method will not work if you change all those longs to uint16_ts, because C performs type promotions when passing integers to functions, and uint16_t’s type may (likely) wish to promote to something else, and not necessarily the same thing across different compilers or platforms, and va_args does not like you if you try.

A Significantly Less Evil Method

Here is a method proposed by o11c as a comment. It uses an array of (function pointer, value) elements, which solves a number of the problems with my dumb varargs version above. Nice!

typedef long (*fn_t)(long, long);

long add(long a, long b) {return a + b;}
long multiply(long a, long b) {return a * b;}
long divide(long a, long b) {return a / b;}

struct apply_t
{
  fn_t fn;
  long value;
};

long apply(long a, struct apply_t fvs[])
{
  for (int n = 0;  fvs[n].fn;  n++)
  {
    a = fvs[n].fn(a, fvs[n].value);
  }
  return a;
}
#define apply(a,...) apply_(a, __VA_ARGS__)
#define apply_(a,...) apply(a, (struct apply_t[]){__VA_ARGS__, {.fn=NULL}})

#define Add(a)       apply(a,{.fn=add,
#define To(b)                         .value=b}
#define ThenMultiplyWith(c) ,{.fn=multiply,.value=c})
#define ThenDivideBy(c)     ,{.fn=divide,.value=c})


#include <stdio.h>

int main()
{
  printf("%lu\n", Add(12)To(8)ThenMultiplyWith(5) );
  printf("%lu\n", Add(12)To(8)ThenDivideBy(5) );
  return 0;
}

This requires C99 minimum, which should not be a problem for anyone. Obviously it is much less friendly to use without the evil macros, but serious errors are eliminated and much more intelligent error messages can be generated by the compiler. Win!

Conclusion

Ultimately, trying to make things look pretty is typically a waste of time just for some fragile code.

Standard C idioms are, if written correctly, very easy to read. Weird stuff is confusing. Honestly, anything that looks like Flux(x)To(y)ThenQuuxify(z) is obtuse enough that anyone who encounters it would be very hesitant to touch it, let alone replicate it, especially when the following is obvious and unambiguous:

quuxify(flux(x,y),z);

I may not know what happens when I quuxify something, or what fluxing x and y means, but I am not the least bit concerned about what happens when or how the operations are related.

Finally, remember that, unlike everything else in C, macros are not scoped. They are useful and necessary at times, but using a macro like “Add” has a higher-than-usual likelihood of causing failure somewhere when you least expect it.



Edited: Got upvoted, so I came back and read over the “solutions” I presented here and realized I made a mistake that I could have avoided: I have now fixed the apply macro to terminate the apply() function’s argument list with two NULLs. Why? Just an extra layer of protection against your friendly neighborhood UB segfault / access violation.

To be clear, there is nothing I can do to absolutely force the user to pass a correct list of (integer, function pointer, ..., integer, NULL), but I can design my macros to make it more difficult to mess things up.

like image 30
Dúthomhas Avatar answered Feb 05 '26 05:02

Dúthomhas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!