Constant expressions to detect the presence and value of macros

5

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__)),
};
    
asked by PaperBirdMaster 28.02.2017 в 12:50
source

1 answer

5

TO_STRING(X) will only return the name X if the value is not defined:

std::cout << TO_STRING(__cplusplus) << " " << STRINGIFY(__cplusplus) << '\n'
          << TO_STRING(NO_EXISTO) << " " << STRINGIFY(NO_EXISTO) << '\n';

Exit:

201500L __cplusplus
NO_EXISTO NO_EXISTO

A first approach that I can think of to solve part of the problem is to verify if the name has an associated value:

template <int SIZE1, int SIZE2 >
constexpr int i(const char (&definition)[SIZE1],const char (&check)[SIZE2])
{
    return std::strcmp(definition,check) != 0;
}

Although honestly, for these cases I prefer to use auto :

constexpr int i(auto definition, auto check)
{
    return std::strcmp(definition,check) != 0;
}

With this we are already able to detect if the name has been defined or not:

#include <iostream>
#include <cstring>

//#define _DEBUG 0
#define DRIVERS 09072007

#define STRINGIFY(X) #X
#define TO_STRING(X) STRINGIFY(X)
#define PARSE(X) TO_STRING(X),#X

constexpr bool i(auto definition, auto check)
{
    return std::strcmp(definition,check) != 0;
}

enum stuff : bool
{
  cpp_version     = i(PARSE(__cplusplus)),
  debug_enabled   = i(PARSE(DEBUG)),
  drivers_version = i(PARSE(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;
}

Exit:

C++ version: 1
Modo debug: 0
Drivers version: 1

The issue of storing the value is somewhat more complex, since with constexpr you can not use the type std::string since it uses dynamic memory ... I will give a return to this point 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:

#define NO_FUNCIONA NO_FUNCIONA

But the chances of this happening I think they are ridiculous.

EDIT:

After the chat conversations and some additional tests I found a possible solution.

For each name there is a pair of values:

  • a Boolean that indicates whether the name in question is defined or not
  • the value of the element (to be taken into account only if the name is defined)

Your most recent solution once my changes are applied:

#include <iostream>

constexpr int power10(int power)
{
    if (power == 0)
        return 1;

    return 10 * power10(power - 1);
}

template<int SIZE_A, int SIZE_B>
constexpr bool same_literals(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
{
    bool result = false;

    if constexpr( SIZE_A == SIZE_B )
    {
        result = true;
        for (std::int32_t index = 0; result && (index < SIZE_A); ++index)
        {
            result = (a[index] == b[index]);
        }
    }

    return result;
}

template <int SIZE_A, int SIZE_B>
constexpr std::int32_t integral_value_or_zero(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
{
    std::int32_t result = 0;

    if ( !same_literals(a, b) )
    {
        for (std::int32_t index = 0; index < SIZE_B; ++index)
        {
            if (b[index] >= '0' && b[index] <= '9')
            {
                result += power10(SIZE_B - index - 2) * (b[index] - '0');
            }
        }
    }

    return result;
}

template <int SIZE_A, int SIZE_B>
constexpr bool bool_value_or_false(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
{
    return integral_value_or_zero(a,b) != 0;
}

template <int SIZE_A, int SIZE_B>
constexpr const char *string_or_empty(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
{
    if (same_literals(a, b))
        return "";

    return b;
}

enum class Tipos
{
  Entero,
  Cadena,
};

template<int SIZE_A, int SIZE_B, Tipos>
struct TiposTraits;


template<int SIZE_A, int SIZE_B>
struct TiposTraits<SIZE_A, SIZE_B, Tipos::Entero>
{
  static auto Func(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
  { return integral_value_or_zero(a,b); }
};


template<int SIZE_A, int SIZE_B>
struct TiposTraits<SIZE_A, SIZE_B, Tipos::Cadena>
{
  static auto Func(const char (&a)[SIZE_A], const char (&b)[SIZE_B])
  { return string_or_empty(a,b); }
};


#define STRINGIFY(X) #X

#define INTEGRAL_VALUE_OR_ZERO(X) integral_value_or_zero(#X, STRINGIFY(X))
#define BOOL_VALUE_OR_FALSE(X) bool_value_or_false(#X, STRINGIFY(X))
#define STRING_OR_EMPTY(X) string_or_empty(#X, STRINGIFY(X))

#define NEW_PAIR(X,T) \
  std::make_pair(!same_literals(#X,STRINGIFY(X)),\
                 TiposTraits<sizeof(#X),sizeof(STRINGIFY(X)),T>::Func(#X,STRINGIFY(X)))

#define _DEBUG FALSE
#define DRIVERS _09072007

enum stuff : int
{
    cpp_version     = INTEGRAL_VALUE_OR_ZERO(__cplusplus),
    debug_enabled   = INTEGRAL_VALUE_OR_ZERO(_DEBUG),
    drivers_version = INTEGRAL_VALUE_OR_ZERO(DRIVERS),
    test = INTEGRAL_VALUE_OR_ZERO(test),
};

namespace properties
{
  const std::string cpp_version = STRING_OR_EMPTY(__cplusplus);
  const std::string debug_enabled = STRING_OR_EMPTY(_DEBUG);
  const std::string drivers_version = STRING_OR_EMPTY(DRIVERS);
  const std::string test = STRING_OR_EMPTY(test);

  auto const Cpp_version    = NEW_PAIR(__cplusplus,Tipos::Cadena);
  auto const Driver_version = NEW_PAIR(DRIVERS,Tipos::Entero);
  auto const dummy          = NEW_PAIR(dummy,Tipos::Cadena);

}

int main()
{
    std::cout << "Cpp_version has value:    " << std::get<0>(properties::Cpp_version) << '\n'
              << "Cpp_version value:        " << std::get<1>(properties::Cpp_version) << '\n'
              << "Driver_version has value: " << std::get<0>(properties::Driver_version) << '\n'
              << "Driver_version value:     " << std::get<1>(properties::Driver_version) << '\n'
              << "dummy has value:          " << std::get<0>(properties::dummy) << '\n'
              << "dummy value:              " << std::get<1>(properties::dummy) << '\n';

    return 0;
}

The output generated by the program will now be the following:

Cpp_version has value:    1
Cpp_version value:        201406L
Driver_version has value: 1
Driver_version value:     9072007
dummy has value:          0
dummy value:              
    
answered by 28.02.2017 / 15:19
source