Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check a value like "#define VERSION 3.1.4" at compile time?

I am adding compile-time checks to my company's C++ projects to make sure the third-party libraries on all development machines and build servers are up-to-date. Most libraries define something like the following for e.g. version 3.1.4:

#define VERSION_MAJOR 3
#define VERSION_MINOR 1
#define VERSION_BUILD 4

This is nice and easy to check using static_assert or preprocessor directives.

Now I am looking at a third-party library that defines a single macro instead:

#define VERSION 3.1.4

How can I verify the value of such a macro at compile time?


With C++11, I could use a constexpr string comparison function, and stringify the macro to check it:

constexpr bool static_equal(const char * a, const char * b)
{
    return (*a == *b) && (*a == '\0' || static_equal(a + 1, b + 1));
}

// stringification functions
#define str(x) #x
#define xstr(x) str(x)

static_assert(static_equal(xstr(VERSION), "3.1.4"), "incorrect version of libwhatever");

But we are using Visual Studio 2013 on the Windows machines, so I can only use the subset of C++11 that it supports. Unfortunately constexpr is not supported.

like image 433
gnome Avatar asked Jun 18 '15 21:06

gnome


People also ask

What is like %% in SQL?

The LIKE operator is used in a WHERE clause to search for a specified pattern in a column. There are two wildcards often used in conjunction with the LIKE operator: The percent sign (%) represents zero, one, or multiple characters. The underscore sign (_) represents one, single character.

Which operator checks a value against a range of values?

The BETWEEN operator is used to select values within a range.


2 Answers

Here is what I am doing now:

#define str(x) #x
#define xstr(x) str(x)

#include xstr(libwhatever.version.is.VERSION.should.be.3.1.4)

Along with this, I add an empty file named libwhatever.version.is.3.1.4.should.be.3.1.4 to the project. So if the version is correct, the preprocessor will successfully include this file. Otherwise, it will fail with "Cannot open 'libwhatever.version.is.2.7.2.should.be.3.1.4', no such file or directory". And failing the build with a somewhat meaningful message is what counts in the end.

Of course this approach is not very flexible; for instance I cannot check for a minimal version, or a range of versions. But for me it is sufficient to be able to check the exact value.

This seems to work with Visual C++ as well as g++. I am not sure whether the behavior is entirely well-defined according to the standard, though.

like image 192
gnome Avatar answered Oct 19 '22 06:10

gnome


You can't in the preprocessor, but you can abuse type traits!

VS 2013 seems to support variadic templates. Try using the macro CSTRING at https://stackoverflow.com/a/15912824/2097780 (you should be able to replace constexpr with const and have the code still work) and doing something like:

#define STRT(x) decltype(CSTRING(x))
static_assert(std::is_same<STRT(VERSION), STRT("3.1.4")>::value, "incorrect version of libwhatever");

EDIT: That doesn't work. However, if your compiler compiles this without errors:

extern const char data[] = "abc";
template <char C> struct x {
    static const char c = C;
};
char buf[(int)x<"ABC123"[0]>::c];
int main() { return (int)buf; }

Then you can try this:

#include <type_traits>
#define VERSION 1.2.3
#define STR2(x) #x
#define STR(x) STR2(x)

template <char...> struct ststring;

// https://stackoverflow.com/a/15860416/2097780

#define MACRO_GET_1(str, i) \
    (sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
    MACRO_GET_1(str, i+0),  \
    MACRO_GET_1(str, i+1),  \
    MACRO_GET_1(str, i+2),  \
    MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
    MACRO_GET_4(str, i+0),   \
    MACRO_GET_4(str, i+4),   \
    MACRO_GET_4(str, i+8),   \
    MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
    MACRO_GET_16(str, i+0),  \
    MACRO_GET_16(str, i+16), \
    MACRO_GET_16(str, i+32), \
    MACRO_GET_16(str, i+48)

#define MACRO_GET_STR(str) MACRO_GET_64(str, 0), 0

static_assert(std::is_same<ststring<MACRO_GET_STR(STR(VERSION))>,
                           ststring<MACRO_GET_STR("1.2.3")>>::value,
              "invalid library version");
like image 26
kirbyfan64sos Avatar answered Oct 19 '22 05:10

kirbyfan64sos