#Define for building functions alternative

Hi,

So, I'm working with a piece of code that is not mine, but uses

#define SPELL(func) i32b func(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type)

as a type of "function factory" that spits out named functions like so:

SPELL(spell_acid_blast)

if I'm understanding correctly will get expanded to

i32b spell_acid_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type)

I feel like there should be a way to do this same thing with templates, or constexprs or w/e, but I can't seem to remember a way.

Best,

Aaron
So this is an easy way to declare (but not define) lots of functions with identical return and identical parameters, but different names?

1
2
3
i32b spell_acid_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type);
i32b spell_fire_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type);
i32b spell_ice_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type);


and so on?
Last edited on
function pointers can do this, that would work with your const idea. But that would share the same body for all... how are the bodies handled? This is not the same thing as what you have right now, depends on the bodies!!

1
2
3
4
5
6
7
8
9
10
11
12
13

void foo()
{
  cout << "foo" << endl;	
}

void (*bar)() = foo; //rename foo function to bar, but its the same function

int main()
{
  bar();		
}
Last edited on
bodies are handled like this:

1
2
3
4
5

SPELL(spell_acid_blast)
{
  return single_target_spell(level, ch, victim, type, DAMDICE(level, 20, 60, 11), SPELL_ACID_BLAST, SAVING_REFLEX, 0, cb_acid_scarring);
}


Last edited on
Maybe this can be done via inheritance from a base `Spell` class with virtual `Execute()` function where children class are named spells. . .
best as I can tell from what I see here, then, you could indeed do it with function pointers. Its not the only way, but it may be the most direct and simple way.

another thought: if it works, maybe no touchy? Sure, its old-school and crude, but I prefer to leave working code alone regardless, unless there is a need...
Last edited on
@Aaron Vienneau,

This is a C style implementation from the 80's or 90's. It barely saves a little bit of typing, which could just as well be served by copy/paste, which is basically what such defines do at compilation.

They also eliminate the 'self documenting' nature of the signature, such that you can't tell, without referencing the original macro, what parameters (and their names) are part of the function signature.

Templates won't do this, as they are a means of generating functions by type, and I don't see any types that mutate in a meaningful way. Your task, it appears, is to call one of a few select functions based on...what, I can't really see because of the obscuring nature of macros.

Pointers to functions don't really work by themselves because the function you're pointing to doesn't match the signature of the macro. Pointers to functions can't do that.

Constexpr has little to offer here.

Maybe you can define a collection (vector/array) of some kind of mapping which converts a generic function (matching that of this macro) to a call mapping/supplying the missing parameters to the target (single_target_spell in your example) as a series of function objects. This reduces this "thunk" you have here, where a function calls a function just to adapt some parameters like this, into a generic call by ID, where the ID is the entry in the container identifying the target function (single_target_spell) and the parameters you have for it.

Function objects like this might invoke std::function or std::bind or related standard library features.
Pointers to functions don't really work by themselves because the function you're pointing to doesn't match the signature of the macro. Pointers to functions can't do that.

but they do all have the same signature once it creates them. You would just need to write 1 copy of the function, and then rename it over and over, which is all this is doing, if i read it right? Or did I miss something? the one function may have to have some defaulted params, so you can leave them out in some calls?

Function objects like this might invoke std::function or std::bind or related standard library features.

That would be cleaner if it will do the trick.
Last edited on
@jonnin,

but they do all have the same signature once it creates them. You would just need to write 1 copy of the function, and then rename it over and over, which is all this is doing, if i read it right? Or did I miss something?


The function generated by the macro does, but we only have one theoretical example of what the function body does, so we can't say that is universal, or that the various substitutions or additional parameters used to call that "interior" function have unique specializations.

So, that would make all the difference. If they are the same, then there's hardly a reason to bother with any of it, just call the target functions appropriately, but I sense that isn't always the case.

fair enough. I assumed the bodies were the same because it didn't make sense to me to go to all that trouble otherwise, but you are right, we don't know...

Niccolo wrote:
This is a C style implementation from the 80's or 90's. It barely saves a little bit of typing, which could just as well be served by copy/paste, which is basically what such defines do at compilation.


Yeah, you could cut/paste or write them all out like Repeater suggests:
1
2
3
i32b spell_acid_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type);
i32b spell_fire_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i16b type);
i32b spell_ice_blast(i32b level, Charact *ch, Charact *victim, Object *obj, const char *arg, i32b type);


Right? Except there's a problem with my code above (NOT in Repeater's original code): the prototypes aren't the same.

And therein lies the advantage of the macro: it provides a way to guarantee the exact same signature for each function, which means you can create a pointer to a spell function and point it at any of these spells. To me, that's a valid reason to use the macro, but it also hints a more appropriate implementations, perhaps a base class with a virtual "spell" method.

This may be veering off the path of the thread, but....

@dhayden,

Look carefully at your examples. The macro of the OP's example can't generate the second prototype "spell_fire_blast", as there is no way to specify a difference of the last parameter.

I don't have a clue from where the notion of the "spell_fire_blast" prototype uniqueness came, and perhaps I missed that in the thread somewhere above.

As such, cut and paste of the tail, from the parenthesis to the semicolon, is really all that's required to replicate what the macro does.

I submit that doesn't do much work. The actual work is at the function bodies, where this "generic" interface function is specialized to some (unknown pattern) of calls to some other API.

These are, apparently, adapter functions.

The underlying objectives and design concepts are not shown in this discussion. What we have is just talk about a macro, which we all know has been the origin of significant bugs and obfuscation, at least as often as it has been of help. I remember, I used the notion liberally before the late 80's as a C programmer, and we considered some clever macro usage as a kind of magic.

Here, all it does is save a bit of typing while obfuscating what the incoming parameters are that mate to whatever the interior calls require.

If it were possible, relative to my exchange with @jonnin, to know the entire pattern set for these "thunks" or "adapters" which apparently unify the calling signature for a disparate collection of functions, we might then realize there are, say, 25 functions with only 3 different patterns. We could then create 3 adapters (probably templates) that could be used as callable objects (pick the favorite among std::bind, std::function, even pointer to member function if that works) in those 3 styles and eliminate any need for the macro.

If we realize there are 25 different adapters, there's no choice but to write out the tedium in these function bodies, where the macro saves very little typing time compared to the clarity of seeing the incoming parameters, and that can be pasted from a previous example, just changing the name of the function as the macro itself does.

If, as @jonnin and I exchanged, it does turn out there is only 1 adaptation (that is, the OP's post showing one example body is all there is), there was never a reason for the macro in the first place, as all of those functions could be arranged for calling by some simple callable object, due to that uniformity.

So, we have a small matrix. There is a list of names, as we supply to the macro. They're the names of the function signatures he macro generates. These might be shown as the rows of a matrix.

Then, we have the adaptive signatures, that is the signature of the function being called as in the body of the OP's example. There could be 1 or more such columns.

That structure then shows how we consider the design. In this discussion we're looking at a simple, singular application. The macro does appear to be specific to some application's requirements. The overall concept is far more generic, though, and therefore moves the discussion toward the generic notions.

When we look at that matrix I propose, should it resolve to a 1 column matrix, then the macro has no purpose because no adaptations mutate. One could write one adapter to be reused for all calling situations (since there is only 1), used in any convenient "list" of callable objects.

Where there are as many columns as there are rows, it implies there's no leverage, and all translation is tedium - everything on the matrix is specialized, and the matrix is probably diagonal (or easily arranged to be diagonal).

If it is possible that the signature of the OP's macro mutates, then there would either be more parameters to the macro, or there would have to be multiple macros, which correspond to multiple pages of the matrix I proposed.

If the objective is to save typing, I submit this discussion is too focused on the macro itself, while the design requires a larger and deeper view into what is actually required here, from soup to nuts, so as to propose something that provides more leverage (even less typing) and may be far more generic (as this notion comes up in lots of examples).

Edit:

I just realized you're making a point about that situation that may arise when the signatures are not the same due to an error in repeated typing, but that's why cut/paste is about as effective as the macro itself.

I submit that trades one problem for another, though, in that what one reads here:

1
2
3
4
SPELL(spell_acid_blast)
{
  return single_target_spell(level, ch, victim, type, DAMDICE(level, 20, 60, 11), SPELL_ACID_BLAST, SAVING_REFLEX, 0, cb_acid_scarring);
}


now no longer shows the incoming parameters due to obfuscation.

My thinking is that this pattern shows a hint that something deeper will yield far better advantages than the macro can.




Last edited on
it may also be possible to use the existing code to just generate what you need. There is probably a way to rewrap the macros you have with print statements that write the code, generating the .h and .cpp stuff for you, without having to write anything. Then just use the generated files, or adapt them, or study them to see what you really need to do.. or you may be able to extract it, I think theres a way to just run the preprocessor, haven't tried that in a long time..
Last edited on
Topic archived. No new replies allowed.