Doc No: SC22/WG21/N1617 J16/04-0057 Date: March 23, 2004 Project: JTC1.22.32
Reply to: Robert Klarer klarer@ca.ibm.com
Dr. John Maddock john@johnmaddock.co.uk
Beman Dawes bdawes@acm.org
Howard Hinnant hinnant@twcny.rr.com

Proposal to Add Static Assertions to the Core Language (Revision 2)

This is a revision of paper N1604 in the pre-Sydney mailing, which is itself a revision of paper N1381 in the pre-Santa Cruz mailing. Since the previous revision, proposed working paper wording has been amended to reflect suggestions and guidance from the committee.

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 existence of the Boost Static Assertion Library, which is widely used in other Boost libraries. The Loki library, which is not currently a part of Boost, has a similar facility, in the form of a STATIC_CHECK macro. The book Generative Programming, by Czarnecki and Eisenecker, uses template metaprogramming techniques to simulate static asserts, for example, when doing buildability checking in the middle stages of configuration generation.

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 make C++ easier to teach and learn by allowing C++ Standard Library implementations to detect and diagnose common usage errors
  3. it would remove embarrassments by supporting programming techniques in the core language that today rely on the preprocessor

2. The Proposal

2.1 The Substance of this Proposal

A static_assert-declaration takes the following form:

               static_assert ( constant-expression  ,  string-literal ) ;

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

If the constant-expression in the static assertion evaluates as 0, the compiler will issue a diagnostic message containing the literal. Otherwise, the static_assert-declaration has no effect.

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

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");
};

2.2 Proposed Wording

To Table 3 in clause 2.11, Keywords [lex.key], add the new keyword "static_assert"

To clause 7, Declarations [dcl.dcl], paragraph 1, add an additional entry to block-declaration:

static_assert-declaration

To clause 9.2, Class members [class.mem], initial paragraph, add an additional entry to member-declaration

static_assert-declaration

To clause 7, Declarations [dcl.dcl], paragraph 1, add an additional outer level grammar element:

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

To clause 7, at a location to be determined by the Project Editor, add a new section or sub-section:

7.? static_assert declaration

A static_assert-declaration shall have no effect. The constant-expression shall be an integral constant-expression (5.19 [expr.const]) whose type is convertible to bool and yields true when so converted. If the constant-expression yields false, the resulting diagnostic message (1.4.2) includes the text of the string-literal, with the exception that characters not in the basic source character set (2.2) are not required to appear in the diagnostic message.

2.3 Examples

2.3.1 Static assertions at namespace scope


// 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.");

2.3.2 Static assertions at class scope


template <class CharT, class Traits = std::char_traits<CharT> >
class basic_string {
        static_assert(tr1::is_pod<CharT>::value,
                      "Template argument CharT must be a POD type in class template basic_string");
        // ... 
};

2.3.3 Static assertions at block scope


#include <sys/param.h> // for PAGESIZE

class VMMClient {
public:
        int do_something()
        {
                struct VMPage {
                        // ...
                };
                static_assert(sizeof(VMPage) == PAGESIZE,
                              "Struct VMPage must be the same size as a system virtual memory page.");

                // ...
        }

        // ...
};

2.3.4 Double-checking complicated compile-time computation results


template <class T>
class A {
private:
        struct impl_1 {...};
        struct impl_2 {...};
        static const unsigned sz1 = sizeof(impl_1) < sizeof(impl_2) ?
                                    sizeof(impl_2) : sizeof(impl_1);
        static const unsigned sz2 = sizeof(T);
        static const unsigned sz3 = sz1 + (sz2-1) & ~(sz2-1);
        struct impl {
                union {
                        impl_1 a;
                        impl_2 b;
                };
                T data_[sz3];

                static_assert(sizeof(data_) >= sizeof(impl_1),
                              "Design error:  Assumption compromised");
                static_assert(sizeof(data_) >= sizeof(impl_2),
                              "Design error:  Assumption compromised");
        };
};

2.3.5 Double-checking design assumptions


template <class T, bool>
class container_impl {
        static_assert(!std::tr1::is_scalar<T>::value,
                      "Implementation error: this specialization intended for non-scalars only");
        // ...
};

template <class T>
class container_impl<T, true> {
        static_assert(std::tr1::is_scalar<T>::value,
                      "Implementation error: this specialization intended for scalars only");
        // ...
};

template <class T>
class container : private container_impl<T, std::tr1::is_scalar<T>::value> {
        // ...
};

3. Comparing Static Assertions to Concepts

This proposal does not compete with current proposals to introduce core language support for concepts to the C++ language. The introduction of intrinsic support for concepts to C++ will not eliminate the need for static assertions. Static assertions solve a different set of problems than intrinsic support for concepts will solve, and they facilitate styles of programming that concepts do not:

3.1 Diagnosing Type Errors that Concepts Cannot

There are some conceptual constraints that can not be checked via a "functional" check. One example of such a constraint is that a type template argument is a POD:

template <ValueType charT, StringTraits traits>
class basic_string {
        // ...
};

The constraints ValueType and StringTraits can verify most of the requirements, but basic_string also requires that charT is a POD, indeed rather nasty things may occur silently if that is not the case, the obvious fix is:

template <ValueType charT, class traits>
class basic_string {
static_assert(tr1::is_pod<charT>::value,
              "Template argument charT must be a POD type in class template basic_string");
        // ...
};

3.2 Limitations of Static Assertions

A static assertion whose constant-expression depends on one or more template parameters will not be checked by the compiler until the corresponding template is instantiated. This is later than is desired for many cases. Intrinsic language support for concepts will permit the violation of generic concepts to be detected, in many cases, before template instantiation occurs.

Successful use of static assertions to verify complicated cases will often involve the use of template metaprogramming or other metaprogramming techniques. Intrinsic language support for concepts will likely permit easier expression of template constraints in many cases.

4. Interactions and Implementability

4.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.

4.2 Implementability

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

Experimental implementations of the static_assert-declaration exist in development versions of both the IBM and Metrowerks C++ compilers. These implementations correctly handle all of the cases identified in this paper.

In the case of each compiler, the implementation of this feature caused no regressions to be reported when validated against C++ language conformance test buckets containing many thousands of test cases

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

4.3 Example Output

The IBM implementation produces the following diagnostic output when compiling the example that appears below:

"section_4_3.C", line 8.9: 1540-5220 (S) ""Template argument CharT must be a POD type in class template basic_string""
"section_4_3.C", line 22.28: 1540-0700 (I) The previous message was produced while processing "class std::basic_string<char,std::char_traits<char> >".
"section_4_3.C", line 20.5: 1540-0700 (I) The previous message was produced while processing "main()".

#include <type_traits>
#include <iosfwd>

namespace std {

template <class CharT, class Traits = std::char_traits<CharT> >
class basic_string {
        static_assert(tr1::is_pod<CharT>::value,
                      "Template argument CharT must be a POD type in class template basic_string");
        // ...
};

}

struct NonPOD {
        NonPOD(const NonPOD &) {}
        virtual ~NonPOD() {}
};

int main()
{
   std::basic_string<char> bs;
}