should i use static constexpr instead #define () and naming convection

Pages: 12
in good old c we used to just have
 
#define SOME_GLOB_CONSTANT 5 


but now days we also have
1
2
3
4
5
6
7
8
9
10
const int SOME_LOCAL_CONST = 5;


class class_name {
...
static constexpr int SOME_GLOB_INTERNAL = 5;
static constexpr int SOME_OTHER_INTERNAL = pure_func(3);

...
};


should i use the latter and if so, what naming convetion should i follow (C++CoreGuidelines do not provide an answer)
myself if lean towards declaring static constexpr variable (in classes) capitalized just to be consistent with the old c-style #define

also can should consteval or constinit be used instead for values derived from functions

Thanks!
Last edited on
Macros are the only names that should be in all caps, regardless of whether they're constant-like or function-like. Macros not being in all caps and non-macros being in all caps are both sometimes acceptable (e.g. a macro might replace a non-macro or vice versa as the code evolves, and one might not want to rewrite large portions of code to perform that replacement) but to avoid confusion they're situations that are best avoided.
Last edited on
I use all-caps all the time for global constants...
 
const double AMOUNT_FUNDS {2300.00};

Although I also don't use macros a lot, unless I'm redefining loops or something for simplicity
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

#define loop(count) for(int i=0;i<count;++i)

int main ()
{
	loop (4)
	{		
		std::cout << "loop " << i+1 << " ";
	}
	return 0;
}

Saves a lot of space when all you want to do is print an array or a vector! You can also use it for modifying a loop or vector as well.
Those sorts of complex macros is exactly how you write unreadable code. Each new one you add makes your code a little bit less like C++ and a little bit more like a DSL only you know. At the extreme end of the scale you get things like IOCCC entries.
should i use the latter

should consteval or constinit be used instead for values derived from functions

A macro is basically a "search-and-replace" that the preprocessor does.
With your macro you have plain literal constants in your code.
With declared variable you have explicit type associated with the "literal constant".

The compiler does use that additional metadata. Same with const, constexpr, constinit, consteval.
They express, describe, and narrow down; help compiler to point out unintended.

1
2
3
struct Sample {
  int example() const;
};

That const tells the user that Sample does not change when they call example(). That they can call example() on constant object.
That const lets the compiler to tell the writer of example()'s implementation: Hey! Do not tweak self!

Similar specialization:
C has one cast. A sledgehammer. C++ has multiple casts. Precision tools.


Yes, it is better to leave macros to where they are necessary (or substantially simpler). Agent max's loop is not.

Yes, specifiers let you be more strict (and probably more optimized).


@agent max:
1. How much space do you save when you write:
1
2
3
#define loop(count) for(int i=0;i<count;++i)

loop (4)
and not just
for(int i=0;i<count;++i)

2. How do I know, when I see code:
1
2
3
4
	loop (4)
	{		
		std::cout << "loop " << i+1 << " ";
	}

where to look for explanation. Some projects have millions of lines of code and multiple maintainers; the define might not be near.
Last edited on
2. How do I know, when I see code:
1
2
3
4
	loop (4)
	{		
		std::cout << "loop " << i+1 << " ";
	}


where to look for explanation.


Agree 100%. And I would consider it extremely bad form to use a variable (i) that is not defined in the function. And what if i were defined in the function, but the loop macro defines one at a tighter scope:
1
2
3
4
5
6
7
8
	void func()
	{
		int i = 10;
		loop (4)
		{		
			std::cout << "loop " << i+1 << " ";
		}
	}

That would be VERY confusing.
Please, please, please don't use macros. Really, they are a big, big problem that will bite you. With that loop definition hiding in some file somewhere and someone writes:

1
2
int loop {};
...


Whoops. Multiple compile errors and everyone is scratching their heads and asking questions on forums like this as to why this seemingly innocuous simple code won't compile. How many hours is spent by how many people trying to figure out the problem......

DON'T DO IT!

IMO, the only time a macro name should be used is for conditional compilation.

As helios says above, by established convention macro names should be all uppercase. So that they don't clash with variable/function/struct names - which should never be all uppercase. When this convention is broken, bad things happen. Microsoft broke this rule in windows.h where it has max() and min() as macros. So if you use windows.h and try to use std::max() or std::min() you get compile errors. Yikes! You have to use #define NOMINMAX before including windows.h to prevent this madness.

DON'T DO IT!
Last edited on
Also, if you must use macros, prefix them in a namespace-like manner for the project in question to avoid name collisions.
e.g. SFML uses macros like SFML_DEFINE_DISCRETE_GPU_PREFERENCE (SFML being the prefix). wxWidgets (which has a lot more use of macros, more similar to Qt) uses wxCAPS macros, https://docs.wxwidgets.org/trunk/group__group__funcmacro__events.html

About original question: Personally, I prefer CamelCaseLikeThis for constants in C++.
Last edited on
What naming convention should I follow?

I don't find a unique naming convention for constants to be useful. If you do, ALL_CAPS is certainly not the best choice, reserve this convention for macro names.

Should consteval or constinit be used instead for values derived from functions?

Use constinit if you require constant-initialization.
Whether a function is consteval or constexpr depends on the function, but you should prefer constexpr in lieu of const on variables when possible.
Last edited on
So if you use windows.h and try to use std::max() or std::min() you get compile errors.
You get them even if you don't use those functions. Doing something as innocuous as
1
2
#include <Windows.h>
#include <algorithm> 
is enough to trigger errors in standard headers. Oh, and the error may go away if you change the inclusion order. Fun times.
if you can avoid a macro, avoid it.** There are times when they make sense (debugging macros specifically, which have access to things you do not normally know like the source code file name you are currently in) are a good example.

Naming conventions are all over the place: you do what you are told for the code base you are in, if it isnt your project. I mean I can soapbox on it.. to me the all caps constant thing is nonsense: a GREAT MANY constants are NOT capitalized: e for example, no one knows what E is. pi is not capitalized in any normal usage. You can list them for days. Most macros are a surrogate for functions* and should follow the same naming convention you use for functions: the user should not care whether it is a function or a macro, why would they?

*assuming you have knocked off using them for constants.

** macros are challenging to debug, difficult for younger coders to read and update (its not taught in depth anymore) and can have type safety issues to name just a few of the issues.

[how to know] where to look for explanation.

this is a non issue on modern IDE. EG visual studio f12 drops you to the code where whatever offending thing you do not know what is, and a mouse over shows what it is as well. This was a problem when we lacked good IDE, or if you are trying to VI a million LOC project (?!).

So if you use windows.h

this is and always has been a huge aggravation. microsoft spent billions recreating things already in the language, like BOOL or various screwy names for integers and pointers. Some of it is legacy (eg max and min macros predate std versions by a goodly amount of time) but a lot of it is just junk.
Last edited on
microsoft spent billions recreating things already in the language, like BOOL or various screwy names for integers and pointers.
The ALLCAPS macros/typedefs Microsoft has is definitely annoying, but to be fair for BOOL: It was C's fault that it wasn't a type.

In general, I order headers that "bleed" the most information last in the #include part of a file. And often, you can #include a small subset of <windows.h> if you need just a type or two declared in a header class/function.
Last edited on
the user should not care whether it is a function or a macro, why would they?


Well because you can't use macros like you can use functions:
1
2
3
4
int x = 1;
// error: extra semicolon
// error: undefined behavior (once you remove the ';')
loop(x++); 

So clearly loop should be distinguishable, either it needs to be in ALL_CAPS or in a typeface that stands out.
Last edited on
error: undefined behavior
I wish...
the all caps constant thing is nonsense


This again has it's origins in macros. Originally, in C constants were specified using a #define macro. #defines were all caps - hence constants were all caps. Now that we can (should) define constants using const T {xyx}; (et al or equivalent) #define isn't used for constants so there's no longer a convention for having constants as all caps.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <limits>
#define loop(count) for(int i=0;i<(count);++i)

int main()
{
	int x = 1;
	loop(x++);

	std::cout << x << '\n';

	std::cout << std::numeric_limits<int>::min() + 1 << '\n';
}


is a syntactically correct program which compiles OK. The ; following loop() just means that the for loop has no body. Within the macro, count, of course, should be used as (count)

On my Windows system, it displays:


-2147483647


which is:

std::numeric_limits<int>::min() + 1

Last edited on
You're right about the ; but it does overflow the signed integer x, which is undefined behavior.
Yep - I just showed what it did on my windows system with VS.
you got me on that, the oddball not-function-like macros maybe all caps or some warning is required. I have not had to deal with anything like that in a very long time -- some really old C when I was young... I would be happy if I never saw anything like that loop macro outside of trolling someone. And yes, the macro vs ; thing is also troublesome sometimes.
Last edited on
@masterAndreas,
Here's the "naming convention" that I use; I think it's a fairly common one:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Non-constant variables:
int number {};
double numTwo {};
std::string name {};

// Global constants:
const int AMOUNT_CARS = 45;
const double FUND_LIMIT = 1200.00;

// Functions:
void set_name (std::string);
int get_name ();
bool is_thousands (int);

// Macros
// Just don't use them, like @jonnin and @seeplus said

// Classes/structures
class ThisClass
{};
struct ThisOtherStruct
{};

// This space reserved for stuff I may have  will have forgotten 


@keskiverto, @helios, @seeplus,
I don't often use weird macros like that loop example. In fact, I haven't used them at all except in a little experiment, mainly because I was trying to figure out what all you could do with them since my CS textbook doesn't cover them much.

@jonnin,
I doubt any serious programmers are dumb enough to do stuff like my loop macro. And if they are, they won't last long. I only used it once, in an experimental program where I was messing around with #define (and getting about 20 errors in the process, so I quit that).
Pages: 12