How to Compare Two Character Strings Statically at Compile Time

How can you compare two character strings statically at compile time

You can do this with C++11 by using a constexpr function:

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

(See a demo)

It's not possible to do this prior to C++11, with the caveat that many compilers will compile equal string literals to be a pointer to the same location. On these compilers it's sufficient to compare the strings directly since they will both be evaluated as equal pointers.

Compile-time assert for string equality

This is only possible with C++0x. No chance with C++03.

EDIT: Constexpr function for C++0x. The following works with GCC4.6, however the Standard is not explicit in allowing it, and a small wording tweak was and is being considered to make the spec allow it.

constexpr bool isequal(char const *one, char const *two) {
*one == *two && (!*one || isEqual(one + 1, two + 1));
}

static_assert(isequal("foo", "foo"), "this should never fail");
static_assert(!isequal("foo", "bar"), "this should never fail");

The compiler is required to track the reference to characters of the string literals already, throughout all the recursions. Just the final read from characters isn't explicitly allowed (if you squint, you can read it as being allowed, IMO). If your compiler doesn't want to accept the above simple version, you can make your macro declare arrays and then compare those

#define CONCAT1(A, B) A ## B
#define CONCAT(A, B) CONCAT1(A, B)

#define CHECK_EQUAL(A, B) \
constexpr char CONCAT(x1, __LINE__)[] = A, \
CONCAT(x2, __LINE__)[] = B; \
static_assert(isequal(CONCAT(x1, __LINE__), CONCAT(x2, __LINE__)), \
"'" A "' and '" B "' are not equal!")

That's definitely fine.

CHECK_EQUAL("foo", "foo"); /* will pass */
CHECK_EQUAL("foo", "bar"); /* will fail */

Note that CHECK_EQUAL can be used inside of functions. The FCD did make a change to allow constexpr functions to read from automatic arrays in their invocation substitution. See DR1197.

Computing length of a C string at compile time. Is this really a constexpr?

Constant expressions are not guaranteed to be evaluated at compile time, we only have a non-normative quote from draft C++ standard section 5.19 Constant expressions that says this though:

[...]>[ Note: Constant expressions can be evaluated during
translation.—end note ]

You can assign the result to constexpr variable to be sure it is evaluated at compile time, we can see this from Bjarne Stroustrup's C++11 reference which says (emphasis mine):

In addition to be able to evaluate expressions at compile time, we
want to be able to require expressions to be evaluated at compile
time; constexpr in front of a variable definition does that
(and
implies const):

For example:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup gives a summary of when we can assure compile time evaluation in this isocpp blog entry and says:

[...]The correct answer - as stated
by Herb - is that according to the standard a constexpr function may
be evaluated at compiler time or run time unless it is used as a
constant expression, in which case it must be evaluated at
compile-time. To guarantee compile-time evaluation, we must either use
it where a constant expression is required (e.g., as an array bound or
as a case label) or use it to initialize a constexpr. I would hope
that no self-respecting compiler would miss the optimization
opportunity to do what I originally said: "A constexpr function is
evaluated at compile time if all its arguments are constant
expressions."

So this outlines two cases where it should be evaluated at compile time:

  1. Use it where a constant expression is required, this would seem to be anywhere in the draft standard where the phrase shall be ... converted constant expression or shall be ... constant expression is used, such as an array bound.
  2. Use it to initialize a constexpr as I outline above.

Compare preprocessor macros for equality

This is a horrible hack, but seems to work for your example for GCC and C11 at least:

#include <assert.h>
#include <string.h>

...

#define STRINGIFY(x) STRINGIFY_(x)
#define STRINGIFY_(x) #x

#define ASSERT_SAME(m1, m2) \
static_assert(strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) == 0, \
#m1"() and "#m2"() differ!")

ASSERT_SAME(GET_PATTERN_01_PATTERNPOINT02Y, GET_PATTERN_02_PATTERNPOINT04Y);

You might need to pass -std=c11 or -std=gnu11, though the latter shouldn't be needed here.

Explanation:

  • STRINGIFY(x) returns the expansion of x as a string literal. We need to do the stringification in two steps using STRINGIFY_() because # suppresses macro expansion. (With one step we'd get "<x>" instead of "expanded version of <x>".)

  • GCC has a built-in version of strcmp() (__builtin_strcmp()) which is used here. It just happens to be able to compare constant strings at compile-time. The code breaks if you pass -fno-builtin (unless you explicitly use __builtin_strcmp()).

  • static_assert is a C11 compile-time assertion.

With the three ingredients above we can stringify the expanded macros (passing some dummy token that's likely to be unique for the argument) and compare the strings at compile-time.

Yes, this is a hack...

In C++11 there are safer ways to compare strings at compile time -- see e.g. this answer.

As a side note, you could do this at run-time too with zero overhead for GCC and Clang. (The version above won't work for Clang as it's pickier about strcmp(...) == 0 not being an integer constant expression as required by static_assert.) A run-time check like

if (strcmp(STRINGIFY(m1(xxx)), STRINGIFY(m2(xxx))) != 0) {
*report error and exit*
}

gets completely optimized out when the macros are equal. Not even the strings are kept in the read-only data segment (just checked). It's a more robust approach if you can live with having to run the program to discover the problem.

Checking that macro parameter is one of the allowed ones?

You could use string views.

#include <string_view>

using namespace std::string_view_literals;

// Note the sv after the string
#define FOO(bar, ...) \
static_assert(bar == "foo"sv || bar == "bazz"sv, "Invalid value for bar") \
...

The expression "foo"sv invokes a literal operator. It constructs a std::string_view from "foo". std::string_view has overloaded == operators for comparing with strings. These overloaded operators are constexpr which means that they can be evaluated at compile time.

How to compare two preprocessor macros with the same name?

Include the first header. Then save the value of the macro to a constexpr variable:

constexpr auto foo = MY_MACRO;

Then include the second header. It should silently override MY_MACRO. If your compiler starts complaining, do #undef MY_MACRO first.

Then compare the new value of the macro with the variable using a static_assert:

static_assert(foo == MY_MACRO, "whatever");

Efficiency of strings as constants?

If all your string constants are in the same file, they might be folded into the same memory location. If they are in several files that get compiled separately, or if you read them in dynamically, they won't be.

Since you're doing C(++), you can compare strings in two ways (and you haven't said anything about it in the question): pointer identity (==) or char-by-char comparison (strncmp). If you're doing the former, it is extremely unreliable as it depends on the fickles of the compiler (and is certain to fail in the second case); if latter, then you know string comparisons will be taking place.

Save yourself the headache and do it the right way that you already know to be the right way: enums or constants. Convert once at input/output, then you can handle them as numbers internally, which is fast and safe.



Related Topics



Leave a reply



Submit