Integral Constant Expressions are used in many places in C++; as array bounds, as bit-field lengths, as enumerator initialisers, and as arguments to non-type template parameters. However many compilers have problems handling integral constant expressions; as a result of this, programming using non-type template parameters in particular can be fraught with difficulty, often leading to the incorrect assumption that non-type template parameters are unsupported by a particular compiler. This short article is designed to provide a set of guidelines and workarounds that, if followed, will allow integral constant expressions to be used in a manner portable to all the compilers currently supported by boost. Although this article is mainly targeted at boost library authors, it may also be useful for users who want to understand why boost code is written in a particular way, or who want to write portable code themselves.
Integral constant expressions are described in section 5.19 of the standard, and are sometimes referred to as "compile time constants". An integral constant expression can be one of the following:
const int my_INTEGRAL_CONSTANT = 3;
struct myclass
{ static const int value = 0; };
struct myclass
{ enum{ value = 0 }; };
sizeof
expression, for
example:sizeof(foo(a, b, c))
static_cast
, where the
target type is an integral or enumerator type, and the
argument is either another integral constant expression,
or a floating-point literal.INTEGRAL_CONSTANT1 op INTEGRAL_CONSTANT2
p
rovided that the operator is not an assignment
operator, or comma operator.op INTEGRAL_CONSTANT1
provided that the operator is not the increment or
decrement operator.
The following guidelines are declared in no particular order (in other words you need to obey all of them - sorry!), and may also be incomplete, more guidelines may be added as compilers change and/or more problems are encountered.
When declaring constants that are class members always use the macro BOOST_STATIC_CONSTANT.
template <class T> struct myclass { BOOST_STATIC_CONSTANT(int, value = sizeof(T)); };
Rationale: not all compilers support inline initialisation of member constants, others treat member enumerators in strange ways (they're not always treated as integral constant expressions). The BOOST_STATIC_CONSTANT macro uses the most appropriate method for the compiler in question.
Don't declare integral constant expressions whose type is wider than int.
Rationale: while in theory all integral types are usable in integral constant expressions, in practice many compilers limit integral constant expressions to types no wider than int.
Don't use logical operators in integral constant expressions; use template meta-programming instead.
The header <boost/type_traits/ice.hpp> contains a number of workaround templates, that fulfil the role of logical operators, for example instead of:
INTEGRAL_CONSTANT1 || INTEGRAL_CONSTANT2
Use:
::boost::type_traits::ice_or<INTEGRAL_CONSTANT1,INTEGRAL_CONSTANT2>::value
Rationale: A number of compilers (particularly the Borland and Microsoft compilers), tend to not to recognise integral constant expressions involving logical operators as genuine integral constant expressions. The problem generally only shows up when the integral constant expression is nested deep inside template code, and is hard to reproduce and diagnose.
Don't use any operators in an integral constant expression used as a non-type template parameter
Rather than:
typedef myclass<INTEGRAL_CONSTANT1 ==
INTEGRAL_CONSTANT2> mytypedef;
Use:
typedef myclass< some_symbol> mytypedef;
Where some_symbol
is the symbolic name of a an
integral constant expression whose value is (INTEGRAL_CONSTANT1
== INTEGRAL_CONSTANT2).
Rationale: the older EDG based compilers (some of which are used in the most recent version of that platform's compiler), don't recognise expressions containing operators as non-type template parameters, even though such expressions can be used as integral constant expressions elsewhere.
Always use a fully qualified name to refer to an integral constant expression.
For example:
typedef
myclass< ::boost::is_integral<some_type>::value> mytypedef;
Rationale: at least one compiler (Borland's), doesn't recognise the name of a constant as an integral constant expression unless the name is fully qualified (which is to say it starts with ::).
Always leave a space after a '<' and before '::'
For example:
typedef
myclass< ::boost::is_integral<some_type>::value> mytypedef;
^
ensure there is space here!
Rationale: <: is a legal digraph in it's own right, so <:: is interpreted as the same as [:.
Don't use local names as integral constant expressions
Example:
template <class T> struct foobar { BOOST_STATIC_CONSTANT(int, temp = computed_value); typedef myclass<temp> mytypedef; // error };
Rationale: At least one compiler (Borland's) doesn't accept this.
Although it is possible to fix this by using:
template <class T> struct foobar { BOOST_STATIC_CONSTANT(int, temp = computed_value); typedef foobar self_type; typedef myclass<(self_type::temp)> mytypedef; // OK };
This breaks at least one other compiler (VC6), it is better to move the integral constant expression computation out into a separate traits class:
template <class T> struct foobar_helper { BOOST_STATIC_CONSTANT(int, temp = computed_value); }; template <class T> struct foobar { typedef myclass< ::foobar_helper<T>::value> mytypedef; // OK };
Don't use dependent default parameters for non-type template parameters.
For example:
template <class T, int I = ::boost::is_integral<T>::value> // Error can't deduce value of I in some cases. struct foobar;
Rationale: this kind of usage fails for Borland C++. Note that this is only an issue where the default value is dependent upon a previous template parameter, for example the following is fine:
template <class T, int I = 3> // OK, default value is not dependent struct foobar;
The following issues are either unresolved or have fixes that are compiler specific, and/or break one or more of the coding guidelines.
Be careful of numeric_limits
There are three issues here:
template <class T> struct limits_test { BOOST_STATIC_ASSERT(::std::numeric_limits<T>::is_specialized); };
This code fails to compile with VC6 even though no instances
of the template are ever created; for some bizarre reason ::std::numeric_limits<T>::is_specialized
always evaluates to false, irrespective of what the
template parameter T is. The problem seems to be confined to
expressions which depend on std::numeric_limts: for example if
you replace ::std::numeric_limits<T>::is_specialized
with ::boost::is_arithmetic<T>::value
, then
everything is fine. The following workaround also works but
conflicts with the coding guidelines:
template <class T> struct limits_test { BOOST_STATIC_CONSTANT(bool, check = ::std::numeric_limits<T>::is_specialized); BOOST_STATIC_ASSERT(check); };
So it is probably best to resort to something like this:
template <class T> struct limits_test { #ifdef BOOST_MSVC BOOST_STATIC_CONSTANT(bool, check = ::std::numeric_limits<T>::is_specialized); BOOST_STATIC_ASSERT(check); #else BOOST_STATIC_ASSERT(::std::numeric_limits<T>::is_specialized); #endif };
Be careful how you use the sizeof operator
As far as I can tell, all compilers treat sizeof expressions correctly when the argument is the name of a type (or a template-id), however problems can occur if:
Don't use boost::is_convertible unless you have to
Since is_convertible is implemented in terms of the sizeof operator, it consistently gives the wrong value when used with the Metroworks compiler, and may not compile with the Borland's compiler (depending upon the template arguments used).
© Copyright Dr John Maddock 2001
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)