This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 110b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2023-01-15


408. sizeof applied to unknown-bound array static data member of template

Section: 13.7.2.5  [temp.static]     Status: CD2     Submitter: Nathan Myers     Date: 14 Apr 2003

[Voted into WP at March, 2010 meeting.]

Is this allowed?

  template<typename T>
    struct X
    {
        static int s[];
        int c;
    };

  template<typename T>
    int X<T>::s[sizeof(X<T>)];

  int* p = X<char>::s;

I have a compiler claiming that, for the purpose of sizeof(), X<T> is an incomplete type, when it tries to instantiate X<T>::s. It seems to me that X<char> should be considered complete enough for sizeof even though the size of s isn't known yet.

John Spicer: This is a problematic construct that is currently allowed but which I think should be disallowed.

I tried this with a number of compilers. None of which did the right thing. The EDG front end accepts it, but gives X<...>::s the wrong size.

It appears that most compilers evaluate the "declaration" part of the static data member definition only once when the definition is processed. The initializer (if any) is evaluated for each instantiation.

This problem is solvable, and if it were the only issue with incomplete arrays as template static data members, then it would make sense to solve it, but there are other problems.

The first problem is that the size of the static data member is only known if a template definition of the static data member is present. This is weird to start with, but it also means that sizes would not be available in general for exported templates.

The second problem concerns the rules for specialization. An explicit specialization for a template instance can be provided up until the point that a use is made that would cause an implicit instantiation. A reference like "sizeof(X<char>::s)" is not currently a reference that would cause an implicit instantiation of X<char>::s. This means you could use such a sizeof and later specialize the static data member with a different size, meaning the earlier sizeof gave the wrong result. We could, of course, change the "use" rules, but I'd rather see us require that static data members that are arrays have a size specified in the class or have a size based on their initializer.

Notes from the October 2003 meeting:

The example provided is valid according to the current standard. A static data member must be instantiated (including the processing of its initializer, if any) if there is any reference to it. The compiler need not, however, put out a definition in that translation unit. The standard doesn't really have a concept of a "partial instantiation" for a static data member, and although we considered adding that, we decided that to get all the size information that seems to be available one needs a full instantiation in any case, so there's no need for the concept of a partial instantiation.

Note (June, 2006):

Mark Mitchell suggested the following example:

    template <int> void g();

    template <typename T>
    struct S {
      static int i[];
      void f();
    };

    template <typename T>
    int S<T>::i[] = { 1 };

    template <typename T>
    void S<T>::f() {
      g<sizeof (i) / sizeof (int)>();
    }

    template <typename T>
    int S<int>::i[] = { 1, 2 };

Which g is called from S<int>::f()?

If the program is valid, then surely one would expect g<2> to be called.

If the program is valid, does S<T>::i have a non-dependent type in S<T>::f? If so, is it incomplete, or is it int[1]? (Here, int[1] would be surprising, since S<int>::i actually has type int[2].)

If the program is invalid, why?

For a simpler example, consider:

    template <typename T>
    struct S {
      static int i[];
      const int N = sizeof (i);
    };

This is only valid if the type of i is dependent, meaning that the sizeof expression isn't evaluated until the class is instantiated.

Proposed resolution (February, 2010):

  1. Add the following as a new paragraph following 13.7.2.5 [temp.static] paragraph 1:

  2. An explicit specialization of a static data member declared as an array of unknown bound can have a different bound from its definition, if any. [Example:

      template<class T> struct A {
        static int i[];
      };
      template<class T> int A<T>::i[4];    // 4 elements
      template<> int A<int>::i[] = { 1 };  // 1 element, OK
    

    end example]

  3. Change 13.8.3.3 [temp.dep.expr] paragraph 3 as follows:

  4. An id-expression is type-dependent if it contains:

    or if it names a static data member of the current instantiation that has type “array of unknown bound of T” for some T (13.7.2.5 [temp.static]). Expressions of the following forms are type-dependent only if...