4

I would like a static assertion to be sure that a given expression is a string literal. I tried this:

#define SAME_TYPES(x, y)                    __builtin_types_compatible_p(typeof(x), typeof(y))
#define IS_ARRAY(x)                         !SAME_TYPES((x), &(x)[0])
#define IS_COMPILE_TIME_STR(x)              IS_ARRAY(x) && #x[0] == '"'
#define ASSERT_COMPILE_TIME_STR(x)          static_assert(IS_COMPILE_TIME_STR(x), "Only static allocated compile time strings are allowed, but got this: " #x)

/*
 * ASSERT_COMPILE_TIME_STR("hey"); <-- this is fine
 * ASSERT_COMPILE_TIME_STR(1234);  <-- this should fail
 */

Unfortunately, ASSERT_COMPILE_TIME_STR does not work, because #x[0] == '"' does not seem to be a compile-time-constant.

Is there some other way to achieve this? It just has to compile with gcc, so any kind of extensions are fine:)

Thanks a lot

19
  • The method is flawed; you cannot detect whether something is an array using x[0] because not every type has a subscript operator. C is the wrong language for this kind of stuff. Commented Jun 27, 2023 at 13:02
  • The subscript operator is applied to the stringified version of the expression. E.g. for x=123, it will evaluate to "123"[0]. So #x[0] is actually always well-defined (even at compile-time). Commented Jun 27, 2023 at 13:04
  • 1
    Not the subscript operator in &(x)[0], that one is applied straight to 1234 which is why your code fails to compile. Commented Jun 27, 2023 at 13:06
  • 1
    @JanSchultke: That x[0] only tests whether x has a subscript operator is okay because it will not be the only test. From memory, I think there is another question on Stack Overflow about detecting arrays. However, testing for a string literal is harder. Commented Jun 27, 2023 at 13:49
  • 1
    @EricPostpischil the actual goal seems to be getting a sane diagnostic, because the question involves a static_assert with a meaningful error message. It is more important to answer in the spirit of the question than to seek a perceived goal at all cost. Commented Jun 27, 2023 at 14:29

1 Answer 1

7

C allows consecutive string constants which the compiler combines into a single constant. You can exploit this by attempting to put a string constant in line with the expression in question.

#define IS_STR_CONT(x) (void)((void)(x),&(""x""))

A few notes about the above:

  • The (x) on the left side of the comma operator helps to catch certain types of non-expressions such as 0+
  • Taking the address ensures that the result is an lvalue
  • The void cast on both the left side of the comma operator and the full expression prevent warnings about an expression with no effect.

The above accepts this with no warning or error:

IS_STR_CONT("hello");

While generating one or more errors with these:

IS_STR_CONT(123);
IS_STR_CONT(0+);
IS_STR_CONT(+0);
int i=0;
IS_STR_CONT((i++,"xxx"));
IS_STR_CONT(!"abc");
IS_STR_CONT(4 + "abc");
IS_STR_CONT(*"abc");
IS_STR_CONT("ad" == "abc");

This isn't perfect, but should catch most cases.

Sign up to request clarification or add additional context in comments.

5 Comments

This will need to be combined with other tests to exclude non-string-literal possibilities for which IS_STR_CONT(x) does not cause an error, including empty, some expressions (like (void) 0, "a"), a cast like (void) or other partial expressions like 0+, and weirder things like 0);( (which cannot directly be an argument to IS_STR_CONT, but you could #define Y 0);( and then pass Y to IS_STR_CONT).
Surveying the grammar, there are plenty of expressions that pass that test, including E, S where E is an expression and S is a string literal, sizeof S, E ? E : S, !S, &S, *S, (T) S where T is a type, E + S, S < S and the other relational and equality operators, 0 == S, 0 != S, S && S, S || S, E = S and the other assignment operators, and expressions built from those, such as 3 + *S. A few of those are effectively a string literal (E, S is effectively S if not confounded in some way) but most are not.
@EricPostpischil Good points. Added some extra checks as well as a caveat at the end.
@chqrlie Added a case for that as well.
Taking the address is an excellent fix!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.