7

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.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
gnome
  • 301
  • 2
  • 10
  • It might be easier to maintain another file that has the version split up like the way you want them to be. – R Sahu Jun 18 '15 at 21:07
  • In the preprocessor you can't I'm afraid. Comparisons only work on integers and there is no way to split the token 3.1.4. It'll need to be an external text processing step of some sort, or check at run time. VS2015 rc is out and may support constexpr. – Jon Chesterfield Jun 18 '15 at 21:14
  • @RSahu: This is a third-party library. If I wanted to add files to it, I'd have to create custom packages and force the developers to use these. That is much more tedious than downloading from the original sources, or, for Debian and derivates, `apt-get libwhatever-dev`. – gnome Jun 18 '15 at 21:18
  • By the way, `static_assert(xstr(VERSION)[0] == '3', "..")` etc. fails with "C2057: expected constant expression". @jxh: that is also what I get for your suggestion. – gnome Jun 18 '15 at 21:38
  • You may need to add a custom tool to the visual studio IDE that does the check for you. – jxh Jun 18 '15 at 21:54
  • When I tried to compile `#define VERSION 3.1.4` compiler complains about too much decimal points, which compiler are you using? – PaperBirdMaster Jun 19 '15 at 06:45
  • 1
    @PaperBirdMaster: Visual C++ 12.0 (aka 2013) and g++ 4.8.2. I get such errors only when I try to use the macro without stringifying it. – gnome Jun 19 '15 at 08:51

3 Answers3

4

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.

gnome
  • 301
  • 2
  • 10
1

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");
Community
  • 1
  • 1
kirbyfan64sos
  • 10,377
  • 6
  • 54
  • 75
  • `decltype` will check the length of the string literal only, not the character content – Ben Voigt Jun 18 '15 at 21:17
  • @BenVoigt No it won't. I've used this in my code before. `decltype` will return `string<'3', '.', '1', '.', '4'>` or similar. – kirbyfan64sos Jun 18 '15 at 21:18
  • Oh that CSTRING macro is highly magic. No, it won't work without constexpr. – Ben Voigt Jun 18 '15 at 21:24
  • @BenVoigt Even with `static const` in its place? – kirbyfan64sos Jun 18 '15 at 21:27
  • 1
    The problem isn't with the `constexpr` keyword itself, but the fact that without support for arbitrary compile-time constant expressions, the subscripting doesn't produce a compile-time constant expression, and therefore can't be used in a template argument. – Ben Voigt Jun 18 '15 at 21:33
  • I have yet to try and understand what this does in detail, but I copied all the definitions and tried to compile. I get compile errors on the lines with `constexpr` (among others). – gnome Jun 18 '15 at 21:35
  • @gnome Wait, I've got this. Do you have access to Boost::MPL? – kirbyfan64sos Jun 18 '15 at 21:55
  • @gnome Try one thing for me: does `extern const char data[] = "abc"; template struct x { static const char c = C; }; char buf[(int)x<"ABC123"[0]>::c]; int main() { return (int)buf; }` compile? If so, I have a solution. – kirbyfan64sos Jun 18 '15 at 22:58
  • @kirbyfan64sos: I'll try that tomorrow when I'm back at work. But considering that `static_assert("abc"[0] == 'a', "..")` doesn't work, I doubt that it will compile. – gnome Jun 18 '15 at 23:06
  • "C2975: invalid template argument for 'x', expected compile-time constant expression". Ben Voigt is correct, the problem is that subscripting doesn't produce a constant expression. – gnome Jun 19 '15 at 09:28
1

If you right click on your project->Properties->Build Events->Pre Build Event You'll see an option that says "Command Line". You can put a call to another program here.

You could write another program in C++ or any language you prefer that checks your file (or any number of files you want) for "#define VERSION 3.1.4". You can abort your build and put any warnings you want in that program.

here is a tutorial: https://dillieodigital.wordpress.com/2012/11/27/quick-tip-aborting-builds-in-visual-studio-based-on-file-contents/

related reading: https://msdn.microsoft.com/en-us/library/e85wte0k.aspx

I tried messing with the preprocessor commands for a long time, and I couldn't find a way to do it using only preprocessor commands.

sitting-duck
  • 961
  • 1
  • 10
  • 22