Question

I used to think that the answer to this question was "100%", but I've recently been pointed to an example that makes it worth thinking twice. Consider a C array declared as an object with automatic storage duration:

int main()
{
    int foo[42] = { 0 };
}

Here, the type of foo is clearly int[42]. Consider, instead, this case:

int main()
{
    int* foo = new int[rand() % 42];
    delete[] foo;
}

Here, the type of foo is int*, but how can one tell the type of the object created by the new expression at compile-time? (Emphasis is meant to stress the fact that I am not talking about the pointer returned by the new expression, but rather about the array object created by the new expression).

This is what Paragraph 5.3.4/1 of the C++11 Standard specifies about the result of a new expression:

[...] Entities created by a new-expression have dynamic storage duration (3.7.4). [ Note: the lifetime of such an entity is not necessarily restricted to the scope in which it is created. —end note ] If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression returns a pointer to the initial element of the array.

I used to think that in C++ the type of all objects is determined at compile-time, but the above example seems to disprove that belief. Also, per Paragraph 1.8/1:

[...] The properties of an object are determined when the object is created. An object can have a name (Clause 3). An object has a storage duration (3.7) which influences its lifetime (3.8). An object has a type (3.9). [...]

So my questions are:

  1. What is meant by "properties" in the last quoted paragraph? Clearly, the name of an object cannot count as something which is determined "when the object is created"- unless "created" here means something different than I think;
  2. Are there other examples of objects whose type is determined only at run-time?
  3. To what extent is it correct to say that C++ is a statically-typed language? Or rather, what is the most proper way of classifying C++ in this respect?

It would be great if anybody could elaborate at least on one of the above points.

EDIT:

The Standard seems to make it clear that the new expression does indeed create an array object, and not just several objects laid out as an array as pointed out by some. Per Paragraph 5.3.4/5 (courtesy of Xeo):

When the allocated object is an array (that is, the noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array. [ Note: both new int and new int[10] have type int* and the type of new int[i][10] is int (*)[10] —end note ] The attribute-specifier-seq in a noptr-new-declarator appertains to the associated array type.

Was it helpful?

Solution

The terms 'static type' and 'dynamic type' apply to expressions.

static type

type of an expression (3.9) resulting from analysis of the program without considering execution semantics


dynamic type

<glvalue> type of the most derived object (1.8) to which the glvalue denoted by a glvalue expression refers

Additionally, you can see that a dynamic type only differs from a static type when the static type can be derived from, which means a dynamic array type is always the same as the expression's static type.

So your question:

but how can one tell the type of the object created by the new expression at compile-time?

Objects have types, but they're not 'static' or 'dynamic' types absent an expression that refers to the object. Given an expression, the static type is always known at compile time. In the absence of derivation the dynamic type is the same as the static type.

But you're asking about objects' types independent of expressions. In the example you give you've asked for an object to be created but you don't specify the type of object you want to have created at compile time. You can look at it like this:

template<typename T>
T *create_array(size_t s) {
    switch(s) {
        case 1: return &(*new std::array<T, 1>)[0];
        case 2: return &(*new std::array<T, 2>)[0];
        // ...
    }
}

There's little special or unique about this. Another possibility is:

struct B { virtual ~B() {}};
struct D : B {};
struct E : B {};

B *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return new D;
    }
    return new E;
}

Or:

void *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return reinterpret_cast<void*>(new int);
    }
    return reinterpret_cast<void*>(new float);
}

The only difference with new int[] is that you can't see into its implementation to see it selecting between different types of objects to create.

OTHER TIPS

The new-expression doesn't create an object with runtime-varying array type. It creates many objects, each of static type int. The number of these objects is not known statically.


C++ provides two cases (section 5.2.8) for dynamic type:

  • Same as the static type of the expression
  • When the static type is polymorphic, the runtime type of the most-derived object

Neither of these gives any object created by new int[N] a dynamic array type.


Pedantically, evaluation of the new-expression creates an infinite number of overlapping array objects. From 3.8p2:

[ Note: The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released. 12.6.2 describes the lifetime of base and member subobjects. — end note ]

So if you want to talk about the "array object" created by new int[5], you have to give it not only type int[5] but also int[4], int[1], char[5*sizeof(int)], and struct s { int x; }[5].

I submit that this is equivalent to saying that array types do not exist at runtime. The type of an object is supposed to be restrictive, information, and tell you something about its properties. Allowing a memory area to be treated as an infinite number of overlapping array objects with different type in effect means that the array object is completely typeless. The notion of runtime type only makes sense for the element objects stored within the array.

I used to think that in C++ the type of all objects is determined at compile-time, but the above example seems to disprove that belief.

The example you cite is talking about storage duration of the item. C++ recognizes three storage durations:

  1. Static storage duration is the duration of global and local static variables.
  2. Automatic storage duration is the duration for "stack allocated" function-local variables.
  3. Dynamic storage duration is the duration for dynamically allocated memory such as that with new or malloc.

The use of the word "dynamic" here has nothing to do with the object's type. It refers to how an implementation must store the data that makes up an object.

I used to think that in C++ the type of all objects is determined at compile-time, but the above example seems to disprove that belief.

In your example, there is one variable, which has type int*. There is not an actual array type for the underlying array which can be recovered in any meaningful way to the program. There is no dynamic typing going on.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top