I am making great efforts to translate the macros of my projects to constant expressions that can be used with if constexpr
. At the moment I have achieved almost satisfactory results doing some tricks with macros, I start defining macros to transform values to text:
#define STRINGIFY(X) #X
#define TO_STRING(X) STRINGIFY(X)
These macros behave in a surprising way when passing existing or non-existent definitions, for example the following code:
std::cout << TO_STRING(_DEBUG) << '\n';
Show _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 operator #
of the macro STRINGIFY
). I use this trick to create the following one:
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 true value while those that are not defined the opposite value, so I can write the following code with if constexpr
instead of #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 do not like having to delegate to an auxiliary function to translate the values (the function b
), but it is a lesser evil. The biggest problem with this system is that it is only able to detect the presence of macros that begin with a low script ( _
), gives false positives for macros whose value is something that starts with a low hyphen ( _
) and the value of the macro is completely lost since there is no calculable function at compile time 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 previous code 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 __cplusplus
has a numeric value that does not start with a low dash ( _
), it gets the value 1
. The macro _DEBUG
is the same: it has value 0
, which would be like considering that we are not in debug mode but get the value 1
. It happens the other way with the macro DRIVERS
, that when starting with a low script you get the value 0
.
Question.
Is there any way to get the desired output? It would be necessary at least a constexpr
to pass literals from text to number.
What have I tried?
I tried a recursive function, but indexing a text literal is not a constant expression (even with known indexes 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__)),
};