Doc No: SC22/WG21/N1617 J16/04-0057 Date: March 23, 2004 Project: JTC1.22.32
Reply to: Robert Klarer [email protected]
Dr. John Maddock [email protected]
Beman Dawes [email protected]
Howard Hinnant [email protected]
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.
The C++ language currently supports two facilities for testing software assertions:
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:
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:
The addition of static assertions to the core language would have the following effects:
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"); };
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 yieldstrue
when so converted. If the constant-expression yieldsfalse
, 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.
// 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.");
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"); // ... };
#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."); // ... } // ... };
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"); }; };
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> { // ... };
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, StringTraits traits> class basic_string { // ... };
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"); // ... };
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.
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.
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.
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; }