1

Suppose I have a function prototype which defaults several parameters:

bool debug_log(char* text, int len,
               bool   log_always = true,    // Defaults to yes, log
               SRgba* backRgba   = NULL,    // Use default color
               SRgba* foreRgba   = NULL);   // Use default color

If I create a few #defines which reference that function, but populate in color values for their logging types, how can I get that log_always parameter's default value to come through based on how it's defined in the prototype?

#define debug_log_err(a, b) debug_log(a, b, \
                                      true, \
                                      &errBackRgba, \
                                      &errForeRgba)
    
#define debug_log_block(a, b) debug_log(a, b, \
                                        true, \
                                        &blockBackRgba, \
                                        &blockForeRgba)

#define debug_log_highlight(a, b) debug_log(a, b, \
                                            true, \
                                            &highlightBackRgba, \
                                            &highlightForeRgba)

In these cases, everything's in sync presently because the 3rd parameter is passing in true, which is how the prototype defines the default parameter. But if I later go in and decide to set log_always to false, then unless I remember to also update my #define usages, they will then be stale and out of sync.

I'd like some kind of away to do something like this with something like the @ character, which tells the compiler to use whatever the prototype's default value is there, rather than me having to hard-coding it.

#define debug_log_err(a, b) debug_log(a, b, \
/* Let the compiler fill in */        @, \
                                      &errBackRgba, \
                                      &errForeRgba)

#define debug_log_block(a, b) debug_log(a, b, \
/* Let the compiler fill in */          @, \
                                        &blockBackRgba, \
                                        &blockForeRgba)

#define debug_log_highlight(a, b) debug_log(a, b, \
/* Let the compiler fill in */              @, \
                                            &highlightBackRgba, \
                                            &highlightForeRgba)

Does such an ability exist? And if not, this would seem to be a shortcoming in C++, similar to the inability to specify just a few parameter and let the rest all be auto-populated with their defaults when provided.

UPDATE: What I'm getting at is that #defines like this expand out to whatever they expand out to, which is then compiled. So perhaps the question I'm asking is ... is there a way to reference in a function use the intervening default parameters, and I know the answer to that is no. So, this question is mostly commentary that I think that should change and the ability should be added to C++.

9
  • 6
    Don't, just dont use macros for this. Just make functions, or function templates. Read more here : Why are preprocessor macros evil. A bit of a dramatic title but read the answer. Commented Jun 16, 2023 at 18:46
  • 3
    Remember that macros are stupid (in all senses of the word). They just do simple text substitution and nothing more; they have no understanding of the semantics of that text (what it actually means to the program). They don't have any concept of types, default arguments, templates, or anything remotely close to that level. Commented Jun 16, 2023 at 18:53
  • Change the order, put log_always at the end. Now you can use #define debug_log_err(a, b, c) debug_log(a, b, &errBackRgba, &errForeRgba, c) Commented Jun 16, 2023 at 19:19
  • Ripi2, the same issue would exist then for any references to the color parameters if they were no longer NULL by default, but were used in code somewhere because the log_always value needed to be overridden. I really think C++ needs to add the ability to allow the compiler to pull through default prototype value where possible. Commented Jun 16, 2023 at 19:40
  • If you have so many possible combinations, why using macros? Just use the defined function, not its "short-version" by some macro. Commented Jun 16, 2023 at 19:43

2 Answers 2

3

To solve your problem, replace true in the function prototype and the macros with a constant:

    const bool log_always_default = true;  // Defaults to yes, log

    bool debug_log(char* text, int len,
                   bool   log_always = log_always_default,    
                   SRgba* backRgba   = NULL,    // Use default color
                   SRgba* foreRgba   = NULL);   // Use default color

    #define debug_log_err(a, b) debug_log(a, b, \
                                          log_always_default, \
                                          &errBackRgba, \
                                          &errForeRgba)
        
    #define debug_log_block(a, b) debug_log(a, b, \
                                            log_always_default, \
                                            &blockBackRgba, \
                                            &blockForeRgba)
    
    #define debug_log_highlight(a, b) debug_log(a, b, \
                                                log_always_default, \
                                                &highlightBackRgba, \
                                                &highlightForeRgba)

Now if you change log_always_default to false, it will affect all your macros.

As others pointed out, there is no reason to use macros here. You can replace them with inline functions, like:

inline bool debug_log_err(char* text, int len)
{
    return debug_log(text, len, log_always_default, &errBackRgba, &errForeRgba);
}
Sign up to request clarification or add additional context in comments.

Comments

2

Does such an ability exist? And if not, this would seem to be a shortcoming in C++, similar to the inability to specify just a few parameter and let the rest all be auto-populated with their defaults when provided.

Compiler can't guess the position of arguments that you provided, that's why the optional arguments have to be last. It's convention.

Sure, your point is to add an operator for the compiler to know that you are trying to substitute default value provided by function prototype but it's kinda impossible for compiler to actually know what function you are trying to call


// the operator for argument substitution is @

int log(int level, bool flag = false, int color = 0);
int log(int level, int color = 0);

int main() {
    log(0, @, 1); // Obviously, there's only one function, so this call is clear
    log(0, @);    // But considering overloading, this call is ambiguous 
    return 0;
}

so you can see where the operator comes short and could lead to trivial issues when linking.

As for the solution, why not just move the log_always to the back of parameters?

bool debug_log(char* text, int len,
               SRgba* backRgba   = NULL,    // Use default color
               SRgba* foreRgba   = NULL,    // Use default color
               bool   log_always = true,    // Defaults to yes, log
);   

#define debug_log_err(a, b) debug_log(a, b, \
                                      &errBackRgba, \
                                      &errForeRgba, \
                                      true)
    
#define debug_log_block(a, b) debug_log(a, b, \
                                        &blockBackRgba, \
                                        &blockForeRgba)

#define debug_log_highlight(a, b) debug_log(a, b, \
                                            &highlightBackRgba, \
                                            &highlightForeRgba)

3 Comments

The same issue comes in if I want to override and use debug_log("hi", 2, NULL, NULL, false); ... it would require the NULL and NULL to be the hard-coded uses, which I don't want (because they later could also change in the function prototype) to have to populate just because I need to change a later parameter. I think that @ character could be introduced to hold the default parameter when available. And in cases of ambiguous calls, generate the error. My $0.02. :-)
To that I say, have multiple overloads, ones where only colors are defined, and ones where only flags are defined, and the last ones which hold the implementation and have all the parameters required
I appreciate your feedback. I'm a compiler developer, and I think this issue could be resolved. I also believe in expanding out any macros at pre-compile time to generate a new source file, which is the one used during compilation and debugging, one which allows the full expansion to be seen in nearby commented code as it went through each iteration. I don't think we need to limit ourselves to what the standard says today, but if something makes sense given our huge legacy code bases, it should be implemented and added to the standard. Again my $0.02 or maybe $0.01. :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.