I am working hard to translate my project macros into constant expressions that can be used with if constexpr
. At the moment I have achieved almost satisfactory results by doing some tricks with macros, I start by defining some macros to transform values to text:
#define STRINGIFY(X) #X
#define TO_STRING(X) STRINGIFY(X)
These macros behave surprisingly when passing existing or non-existent definitions, for example the following code:
std::cout << TO_STRING(_DEBUG) << '\n';
It shows _DEBUG
if the homonymous macro is NOT defined, while if it is defined it shows the value of the macro. The type of the resulting value will always be a text literal (due to the macro's operator#
STRINGIFY
). I use this trick to create the following enum:
template <int SIZE>
constexpr bool b(const char (&definition)[SIZE])
{
return definition[0] != '_';
}
enum operating_system : bool
{
iOS = b(TO_STRING(__APPLE__)),
Windows = b(TO_STRING(__MINGW32__)),
Linux = b(TO_STRING(__linux__)),
};
With this trick , the macros that are defined will have a true value while those that are not defined will have the opposite value, so I can write the following code with if constexpr
instead of with #ifdef
:
int main()
{
if constexpr (operating_system::Windows)
{
// Cosas especificas de Windows
}
else if constexpr (operating_system::iOS)
{
// Cosas especificas de iOS
}
// Cosas independientes de sistema operativo.
return 0;
}
I don't like having to delegate to a helper function to translate the values (the function b
), but it's a lesser evil. The biggest problem with this system is that it is only able to detect the presence of macros that start with an underscore ( _
), it gives false positives for macros whose value is something that starts with an underscore ( _
) and the value of the macro is lost by complete since there is no compile-time computable function that passes text to number (none of my attempts have been successful).
Therefore the following macros (obviously) do not act as expected:
#define _DEBUG 0
#define DRIVERS _09072007
template <int SIZE>
constexpr int i(const char (&definition)[SIZE])
{
return definition[0] != '_'; // que poner aqui?...
}
enum stuff : int
{
cpp_version = i(TO_STRING(__cplusplus)),
debug_enabled = i(TO_STRING(_DEBUG)),
drivers_version = i(TO_STRING(DRIVERS)),
};
int main()
{
std::cout << "C++ version: " << stuff::cpp_version << '\n'
<< "Modo debug: " << stuff::debug_enabled << '\n'
<< "Drivers version: " << stuff::drivers_version << '\n';
return 0;
}
The code above shows:
C++ version: 1 Modo debug: 1 Divers verson: 0
When the ideal would be to have shown:
C++ version: 201500 Modo debug: 0 Divers verson: _09072007
Since it __cplusplus
has a numeric value that does not start with an underscore ( _
), it gets the value 1
. _DEBUG
The same thing happens to the macro : it has value 0
, which would be like considering that we are not in debug mode but it gets the value 1
. The opposite happens with the macro DRIVERS
, which when starting with an underscore gets the value 0
.
Ask.
Is there a way to get the desired output? It would be necessary at least one constexpr
that passes literals from text to number.
What have I tried?
I have tried a recursive function, but indexing a text literal is not a constant expression (even with indexes known at compile time).
constexpr int power10(int n)
{
if (n == 0)
return 1;
return 10 * power10(n - 1);
}
template <int SIZE>
constexpr int v(const char (&definition)[SIZE], int INDEX)
{
// error: 'definition' no es una expresion constante
constexpr char c = definition[INDEX];
if (INDEX >= 0)
{
if constexpr (c >= '0' && c <= '9')
{
return v(definition, INDEX - 1) + (power10(SIZE - INDEX - 2) * (c - '0'));
}
else
{
return 0 + v(definition, INDEX - 1);
}
}
return 0;
}
template <int SIZE>
constexpr int f(const char (&definition)[SIZE])
{
return v(definition, SIZE - 2);
}
enum operating_system : bool
{
// error: valor de el enumerador para 'iOS' no es una constante integral
iOS = f(TO_STRING(__APPLE__)),
// error: valor de el enumerador para 'Windows' no es una constante integral
Windows = f(TO_STRING(__MINGW32__)),
// error: valor de el enumerador para 'Linux' no es una constante integral
Linux = f(TO_STRING(__linux__)),
};
TO_STRING(X)
will only return the nameX
if the value is not defined:Departure:
A first approach that occurs to me to solve part of the problem is to check if the name has an associated value:
Although honestly, for these cases I prefer to use
auto
:With this we are able to detect if the name has been defined or not:
Departure:
The issue of storing the value is somewhat more complex, since
constexpr
the type cannot be used with itstd::string
since it makes use of dynamic memory... I will give this point a go to see if I can think of a way to extract the value associated with the name.This system will fail if the name and value are the same:
But the chances of this happening I think are ridiculous.
EDIT:
After chat conversations and some additional testing I have found a possible solution.
For each name there is a pair of values:
Your most recent solution after applying my changes:
The output generated by the program will now be the following: