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++.

Rick C. Hodgin
  • 483
  • 5
  • 11
  • 6
    Don't, just dont use macros for this. Just make functions, or function templates. Read more here : [Why are preprocessor macros evil](https://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives). A bit of a dramatic title but read the answer. – Pepijn Kramer Jun 16 '23 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. – Nate Eldredge Jun 16 '23 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)` – Ripi2 Jun 16 '23 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. – Rick C. Hodgin Jun 16 '23 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. – Ripi2 Jun 16 '23 at 19:43
  • What you probably really want is named parameters. C++ doesn't have those but there are tricks to simulate them, such as [here](https://www.fluentcpp.com/2018/12/14/named-arguments-cpp/) (there are many implementations floating around). – n. m. could be an AI Jun 16 '23 at 20:48
  • Are you allowed to use C++20? – Jan Schultke Jun 16 '23 at 21:02
  • Both `debug_log(a, b)` or `debug_log(err, a, b)` wouldn't be hard easy to implement as template functions with the same effect as your code. It would also enable to specify the default parameter value only in a single additional place... Btw: Note that in C++ you'll probably need to use `char const*` instead of `char*`, since the latter prevents you from passing string literals... – fabian Jun 16 '23 at 21:44
  • @JanSchultke Yes, I can use C++20. – Rick C. Hodgin Jun 17 '23 at 01:45

2 Answers2

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);
}
Eugene
  • 6,194
  • 1
  • 20
  • 31
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)
rgnt
  • 551
  • 2
  • 7
  • 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. :-) – Rick C. Hodgin Jun 16 '23 at 19:16
  • 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 – rgnt Jun 16 '23 at 19:32
  • 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. :-) – Rick C. Hodgin Jun 16 '23 at 19:38