Doc. No.: N4433
Date: 2014-04-09
Project: Programming Language C++, Evolution Working Group
Reply To: Michael Price
<michael.b.price.dev@gmail.com>

Flexible static_assert messages

I. Introduction

Currently, the specification for static_assert has two forms, one that takes a string-literal that is used as a diagnostic message and another that takes no diagnostic message at all. The restriction that the diagnostic message be a string-literal is limiting since it doesn't allow useful contextual information to be inserted into the diagnostic message. This paper proposes that we change the specification to take a constant expression that can be contextually converted to a const char*, const wchar_t*, const char16_t*, or const char32_t* instead of a string-literal.

II. Motivation

As a library developer, I'd like to include information in diagnostic messages that indicate that my library was the source of the assertion. With the current specification, this becomes an error-prone exercise. If I were required to "rebrand" my library, it's even worse. With the proposed changes, it's a matter of changing a single line in a header file.

  // mplib.h
  //
  constexpr auto libraryTag = "MP"S; // Presumes a UDL for compile-time string literals
                                     // (see N4121 [2])

  template <typename C, typename M>
  constexpr auto msg (C errorCode, M message)
  {
      return libraryTag + errorCode + ": "S + message;
  }


  // mpsource1.cpp
  //
  #include "mplib.h"
  // ...
  static_assert(sizeof(int) == 32, msg("001"S, "Requires type 'int' to be 32 bits"S));


  // mpsource8534.cpp
  //
  #include "mplib.h"
  // ...
  static_assert(std::is_integral<T>::value, msg("999"S, "T must be an integral type"S);
  

Another use case of such an extension would be to allow type-dependent or implementation-specific information to appear in the diagnostic message.

  // Assume some compile-time formatter function with the following syntax
  //
  template <typename M, typename Ts...>
  constexpr auto format (const M& message, Ts... ts);


  template <typename T, std::size_t N>
  struct limited_buffer {
    unsigned char data[16];

    static_assert(sizeof(T) * N <= sizeof(data),
                  format("You can only fit {0} {1}s into a {2}."S,
                         sizeof(data)/sizeof(T),
                         typeid(T)::name(),    // Assuming this were constexpr
                         "limited_buffer"S
                        )
                 );
  };
  

III. Technical Specification

All edits are against N4140 [1]

Edit paragraph 7.1 [dcl.dcl] as follows.

static_assert-declaration:
static_assert ( constant-expression constant-expression-test ) ;
static_assert ( constant-expression constant-expression-test , string-literalconstant-expression-message ) ;

Edit paragraph 7.1.6 as follows.

In a static_assert-declaration the constant-expression constant-expression-test shall be a constant expression (5.20) that can be contextually converted to bool (Clause 4) , and the constant-expression-message shall be a constant expression (5.20) that can be contextually converted to const char*, const wchar_t*, const char16_t*, or const char32_t* (Clause 4). If the value of the expression constant-expression-test when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and the resulting diagnostic message (1.4) shall include the text of the string-literal value of constant-expression-message after conversion, if one is supplied, except that characters not in the basic source character set (2.3) are not required to appear in the diagnostic message. [ Example:

static_assert(sizeof(long) >= 8, "64-bit code generation required for this library.");
— end example ]

IV. Questions and Answers

Q: What happens if a static_assert fires during the evaluation of the constant-expression-message?   A: Implementation defined behavior.

The current text does not specify what happens if a static_assert fires during the evaluation of the expression being tested. Admittedly, choosing a behavior might be more complicated in this case, but it should still be at the discretion of the implementation.

Q: Why support so many character-types (const wchar_t*, etc...)?   A: Because the grammer for the existing string-literal parameter does.

See [lex.string] [1].

V. Acknowledgements

I would hardly have the time to author these proposals were it not for the generosity of my employer, Lexmark International, who has earned my thanks for their continuing support. I also wanted to extend my gratitude to those individuals who carefully reviewed this paper and provided valuable feedback, Chris Sammis, Ryan Lucchese, and John Drouhard.

VI. References

[1] R. Smith, Working Draft, Standard for Programming Language C++. N4140.
[2] A. Tomazos, Compile-Time String: std::string_literal<n>. N4121.