Doc No: SC22/WG21/N1381
J16/02-0039
Date: September 4, 2002
Project: JTC1.22.32

Reply to: Robert Klarer
IBM
klarer@ca.ibm.com

Dr. John Maddock
john_maddock@compuserve.com

Proposal to Add Static Assertions to the Core Language

1. The Problem

The C++ language currently supports two facilities for testing software assertions:

  1. the assert macro, and
  2. the #error preprocessor directive

Neither of these facilities is appropriate for use in template libraries. To enforce a template parameter constraint, for example, it should be possible to write an assertion that will be tested when the associated template is instantiated. The assert macro tests assertions at runtime, which is far later than would be desired by the author of a template library, and which implies a runtime performance cost. The #error preprocessor directive is processed too early to be of much use in this regard. In particular, the #error directive is processed before templates are instantiated, and hence cannot be used to test assertions involving template arguments.

The need for a solution to this problem is demonstrated by the Boost Static Assertion Library, which is widely used in other Boost libraries, particularly the Boost Concept Check Library (BCCL). The Loki library, which is not currently a part of Boost, has a similar facility, in the form of a STATIC_CHECK macro.

At the time that the Boost Static Assertion Library was developed, the following design requirements for a static assertion checking facility were identified:

  1. all processing must be performed during compile time -- no runtime cost in space or time is tolerable
  2. it must have simple syntax that can easily be taught to beginners
  3. assertion failure must result in a meaningful and informative diagnostic error message
  4. it can be used at namespace, class, or block scope
  5. misuse does not result in silent malfunction, but rather is diagnosed at compile time

The proposal described in this paper satisfies all of these requirements.

The Boost Static Assertion Library and the Loki STATIC_CHECK macro constitute two independent attempts to solve this problem without introducing a change to the specification of the core language, but each has the following inadequacies:

  1. assertion failure produces compile-time diagnostics that are typically uninformative
  2. each uses a macro (BOOST_STATIC_ASSERT, STATIC_CHECK) to generate the types that represent the assertion, and this poses namespace pollution problems

The addition of static assertions to the core language would have the following effects:

  1. it would improve support for library building by allowing libraries to detect common usage errors at compile time,
  2. it would improve support for Generic Programming (GP) by permitting concept checking, which would make GP libraries easier to learn and use by eliminating a common source of errors,
  3. it would make C++ easier to teach and learn by allowing C++ Standard Library implementations to detect and diagnose common usage errors,
  4. it would remove embarrassments by supporting programming techniques in the core language that today rely on the preprocessor

2. The Proposal

2.0 The Substance of this Proposal


New keyword:
static_assert

New grammar productions:

block-declaration:
static_assert-declaration

member-declaration:
static_assert-declaration

static_assert-declaration:
static_assert ( constant-expression , literal ) ;

Intuitively, a static_assert-declaration may appear anywhere that a using-declaration can, including namespace scope, block scope, and class member declaration lists.

The standardese specification of a static_assert-declaration might resemble the following.

"The static_assert-declaration has no effect. The constant-expression shall yield true."

If the constant-expression does not have a value of true, a diagnostic is required, by implication of subclause 1.3.

In a footnote, we can say a bit more about the intention of static_assert:

"It is recommended that, if the constant-expression does not yield
true, the resulting diagnostic message or messages (1.4.2) contain the
text of the literal."

The static_assert-declaration does not declare a new type or object, and does not imply any size or time cost at runtime.

2.1 Basic Cases

Case 1:



// At namespace scope, the static_assertion declaration
// may be used as an alternative to the #error preprocessor
// directive.
//
static_assert(sizeof(long) >= 8, "64-bit code generation not enabled/supported.");

Case 2:



// Function (block) scope
void f()
{
static_assert(sizeof(int) == sizeof(void *), "static assertion");
}

Case 3:

// Class scope

struct Bob
{
static_assert(sizeof(int) == sizeof(void *), "static assertion");

// Member function scope: provides access to member variables
int x;
char c;
int f()
{
static_assert(sizeof(x) == 4, "static assertion");
static_assert(sizeof(c) == 1, "static assertion");
return x;
}
};

Case 4:



// Template class scope
template <class Int, class Char>
struct Bill
{
static_assert(sizeof(Int) == 4, "static assertion");
// should not compile when instantiated
static_assert(sizeof(Int) == sizeof(Char), "static assertion");

// Template member function scope:
// provides access to member variables
Int x;
Char c;
template <class Int2, class Char2>
void f(Int2 , Char2 )
{
static_assert(sizeof(Int) == sizeof(Int2), "static assertion");
static_assert(sizeof(Char) == sizeof(Char2), "static assertion");
}
};

Bill<int, char> b;

2.2 Advanced Cases

The following case demonstrates the use of a static_assert-declaration to enforce a template parameter constraint:



#include <memory>

template <class T1, class T2>
struct TypeIdentity { enum { value = false }; };

template <class T1>
struct TypeIdentity<T1, T1> { enum { value = true }; };

template <class T, class Traits = std::char_traits<T> >

struct CustomString {
// check to ensure that we have a valid char_traits type:
static_assert(TypeIdentity<T, typename Traits::char_type>::value,
"Static Assertion: Invalid char_traits type argument.");
};
int main() {
// the assertion will fail:
CustomString<char, std::char_traits<wchar_t> > c1;
}

This paper does not propose that the core language be extended to support template static_assert-declarations. For example, the following is NOT proposed:



template <typename T>
static_assert(sizeof(int) <= sizeof(T), "not big enough");

There is no demonstrated need for this generality. The proposal described in this paper allows the following, equivalent assertion to be written:



template <typename T>
struct Check {
static_assert(sizeof(int) <= sizeof(T), "not big enough");
};

This approach can be used to construct a library of general, reusable assertion templates. The earlier example can be modified to make use of one such assertion template:



#include <iosfwd>

template <class T1, class T2>
struct TypeIdentity { enum { value = false }; };

template <class T1>
struct TypeIdentity<T1, T1> { enum { value = true }; };

// check to ensure that we have a valid char_traits
template<class T, class Traits>
struct ValidateCharTraits {
static_assert(TypeIdentity<T, typename Traits::char_type>::value,
"Static Assertion: Invalid char_traits type argument.");
};

template <class T, class Traits = std::char_traits<T> >
struct CustomString : ValidateCharTraits<T, Traits> {
};

int main() {
CustomString<char, std::char_traits<wchar_t> > c1;
}

3. Interactions and Implementability

3.1 Interactions

This proposal does not affect or alter any existing language feature. Legacy code is not affected by the introduction of static_assert to the core language, except code in which the word static_assert is used as an identifier. Legacy code that uses the word static_assert as a macro name will not be affected.

3.2 Implementability

Static assertions have been used in the Boost libraries for two years. This proposal is based upon that experience.

An experimental implementation of the static_assert-declaration exists in a development version of the IBM C++ compiler. This implementation correctly handles all of the cases identified in this paper, and it caused no regressions to be reported when validated using a C++ language conformance test bucket containing about 7500 test programs (each of these test programs typically tests multiple cases, so the actual number of cases tested is much higher).

A compiler writer could certainly implement this feature, as specified, in two or three days. Implementation of this feature requires modification of the compiler frontend only; no changes to the backend, runtime library, linker, or debugger are necessary.

3.3 Example Output

The experimental implementation produces the following diagnostic output when compiling the first "advanced" example that appears in section 2.2 of this paper:
"chartraits.C", line 13.3: 1540-5220 (S) ""Static Assertion: Invalid char_traits type argument.""
"chartraits.C", line 20.50: 1540-0700 (I) The previous message was produced while processing "struct CustomString<char,std::char_traits<wchar_t> >".
"chartraits.C", line 19.5: 1540-0700 (I) The previous message was produced while processing "main()"