22

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.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 3
    I think you're confusing `dynamic` and a `static`. Your example will return an `int*`.. regardless of how many indices `rand()` returns. That's dynamic memory allocation.. not dynamic typing. – Simon Whitehead Apr 10 '13 at 23:14
  • I'd almost call it "Type erasure" – Mooing Duck Apr 10 '13 at 23:18
  • For correctionness sake, shouldn't you delete[] the pointer? – Spidey Apr 10 '13 at 23:48
  • @Spidey: Shame on me for that. Of course. Edited, thank you – Andy Prowl Apr 10 '13 at 23:49
  • This reminds me of ISO Pascal, where arrays of different sizes were strictly different types. That made e.g. string processing virtually impossible. – MSalters Apr 11 '13 at 08:05
  • 1
    Using C-style arrays to probe the C++ type system leads to insanity. They have rather quirky properties, inherited from C, that don't fit well into a rigorous system of types. So the answer is "so what?". – Pete Becker Apr 11 '13 at 12:05
  • @PeteBecker: Mine was mostly a theoretical interest. Notice, that the question asks whether there are other such examples of objects whose type is determined only at run-time. Your "so what?" seems to imply that 1) it is indeed the case that the type is determined only at run-time, and 2) there are no other similar cases. Then you could elaborate a bit and make an answer out of it. Also, the point in question 1 remains unclear to me: if type is a "property" of an object, and C++ is statically typed, I wouldn't expect properties to be determined "when the object is *created*" (1.8/1). – Andy Prowl Apr 11 '13 at 12:23
  • 1
    String literals (prior to C++11) are also quirky, in addition to the quirks they get from being arrays. And character literals have a few quirks, too. For understanding the type system, they're distractions, not cleanly integrated parts. Which is why I said "so what"; I find them highly uninteresting. They are what they are, and they don't fit well into a rational system. – Pete Becker Apr 11 '13 at 12:35
  • @PeteBecker: OK, I see your point. But it is still not clear to me what is meant in 1.8/1 by "properties", nor by "when the object is *created*" (according to my intuition, creation happens at run-time) – Andy Prowl Apr 11 '13 at 12:42
  • 3
    You know what? This is a great candidate to enter Stroustrup's official C++ faq. – Spidey Apr 11 '13 at 13:15
  • I think that that sentence is descriptive, not normative. That is, it doesn't impose any requirements; the rules for construction and destruction tell you what an object is and what its "properties" are. But that's just a superficial reading, so don't take it to heart. – Pete Becker Apr 11 '13 at 13:39

3 Answers3

10

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.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    But the array itself is an object and has a type which includes its size, no? From the standard: "If it is an array, the new-expression returns a pointer to the initial element of the array." – Joseph Mansfield Apr 10 '13 at 23:14
  • @sftrabbit: No. You cannot query for the size after the thing is created. It isn't a separate "array" type; it is just a bunch of ints adjacent to each other. – Billy ONeal Apr 10 '13 at 23:15
  • @BillyONeal But that doesn't mean the array isn't there... okay, this quickly became a philosophical debate. – Joseph Mansfield Apr 10 '13 at 23:16
  • 3
    @sftrabbit: There is nothing here that has array type. There are objects arrayed in memory, but I'm not even sure this arrangement constitutes an "array object" that you could safely bind to a reference-to-array variable, even with casting. – Ben Voigt Apr 10 '13 at 23:16
  • @BenVoigt I think the problem is that you simply can't get access to this array object in any meaningful way. You can only ever have a pointer to its first element. There's no way to recover the array type from the pointer type. So yes, I'm agreeing that it doesn't really even make sense to talk about the type of the array. – Joseph Mansfield Apr 10 '13 at 23:17
  • 1
    @sftrabbit: If the array even is an object in the first place. – Ben Voigt Apr 10 '13 at 23:18
  • 1
    @BenVoigt Well here's how the standard phrases it: "Entities created by a new-expression have dynamic storage duration. If the entity is a nonarray object, [...]. If it is an array, [...]" Sounds like it's an object to me. – Joseph Mansfield Apr 10 '13 at 23:18
  • All this basically comes from an example that was given to me by [Johannes Schaub](http://stackoverflow.com/questions/15899369/does-decltype-give-me-an-objects-static-type-or-its-runtime-type/15908015#comment22659837_15899369). – Andy Prowl Apr 10 '13 at 23:18
  • @sftrabbit: Yes, clearly the non-array is an object. The Standard isn't telling us whether the array case is an array object or just a tightly packed group of individual objects. – Ben Voigt Apr 10 '13 at 23:20
  • 1
    @sftrabbit: Yes, it does mean it isn't there. A type that cannot be accessed in any meaningful way is not a type. That is, if you did `decltype(new int[rand()/2])` you will get `int*`, not `int[42]`. – Billy ONeal Apr 10 '13 at 23:22
  • @BillyONeal I agree. That's the real point here. The type is completely meaningless. It cannot in any way affect the program. – Joseph Mansfield Apr 10 '13 at 23:23
  • 6
    `§5.3.4/5`: "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. [...] The *attribute-specifier-seq* in a *noptr-new-declarator* appertains **to the associated array type**." -- The standard *clearly* says that an array object is being created, so this answer doesn't seem right. – Xeo Apr 11 '13 at 03:38
  • @Xeo: Way to quote only part of the paragraph... you left out the part that said the created object does not have array type. – Ben Voigt Apr 11 '13 at 03:44
  • 3
    @Ben: You mean the note? That says that the *expressions* `new int` and `new int[10]` have type `int*`. That isn't relevant for the type of the created object. – Xeo Apr 11 '13 at 03:48
  • @Xeo: Anyway, it's pretty clear that a dynamic type can never be an array type. The address of the array is also the address of its first element, and that has a type known at compile time. – Ben Voigt Apr 11 '13 at 03:50
  • I question the use of the Standard term 'dynamic type' in your answer and your comments -- you seem to be misusing it. E.g. an object of array type is a most derived object, hence it has dynamic type that array type. So there is such a thing as a dynamic array type. – Luc Danton Apr 11 '13 at 04:42
  • For the moment, I unaccepted the answer because what seemed to me like a plausible interpretation of what `new` does (create several objects, but not one array object) is disproved by the paragraph @Xeo quoted in a comment, as far as I can tell. – Andy Prowl Apr 11 '13 at 08:39
  • @Luc: AFAICT, the dynamic type of an array object is the runtime type of its first element (which shares the same address). Array objects are not polymorphic, and thus never have dynamic type different from the static type. – Ben Voigt Apr 11 '13 at 14:22
  • So you see how they have a dynamic type then. All that remains is to fix your answer. – Luc Danton Apr 11 '13 at 14:31
  • @LucDanton: I clarified my answer. There's no object here with a runtime-varying array type. The question was whether any types are generated at runtime or they all exist at compile-time, and there's no type here generated at runtime. (You can have dynamic objects with array type, but it is still a static array type, for example with `p = new int[N][4];` the type of `*p` is an array type, but there no runtime variation in it. – Ben Voigt Apr 11 '13 at 14:49
  • [My objection](http://stackoverflow.com/questions/15938077/to-what-extent-is-c-a-statically-typed-language/15938128?noredirect=1#comment22712697_15938128) is to your co-opting of the Standard term 'dynamic type', which has a very precise meaning that is irrelevant here. 'Runtime-varying array type' does not have such baggage. – Luc Danton Apr 11 '13 at 15:13
9

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.

Community
  • 1
  • 1
bames53
  • 86,085
  • 15
  • 179
  • 244
  • Agreed. In addition, Wikipedia defines "static type system" as "type checking is performed during compile-time as opposed to run-time.", and "dynamic type system" as "the majority of its type checking is performed at run-time as opposed to at compile-time". As C++ does _no_ type checks at runtime, this is all still static. (Unrelated, C++ is weakly typed, but safely typed) – Mooing Duck Apr 11 '13 at 18:46
  • 3
    @MooingDuck: C++ actually can do some limited run-time type checking, in the form of `dynamic_cast`. If the class has a vtable, and the type can't be statically guaranteed, `dynamic_cast` can and does do runtime checks to make sure the thing you're pointing at is, or inherits from, the type you're trying to cast it to. – cHao Apr 12 '13 at 17:38
2

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.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • I know that "ipse dixit" is not exactly the best counter-argument, but here is the [comment by Johannes Schaub](http://stackoverflow.com/questions/15899369/does-decltype-give-me-an-objects-static-type-or-its-runtime-type/15908015#comment22659837_15899369) that made me think about this. I am aware of the three storage durations and about the fact that `new` returns an `int*`. What puzzles me is whether there is a "created object" which has array type but where the size of the array (and therefore the type) is only known at run-time – Andy Prowl Apr 10 '13 at 23:25
  • @R.MartinhoFernandes: Sorry, I still mostly think in C++03 land :) – Billy ONeal Apr 10 '13 at 23:39
  • @Andy: There is no concept of a type at runtime in C++. – Billy ONeal Apr 10 '13 at 23:40
  • 2
    @Billy: Of course there is: `typeid` is one of the major "Runtime Type Information" tools (RTTI). But the types always existed at compile time also, the only thing that changes at runtime is how many objects exist of each type and what pointers (and references) are associated with what objects. – Ben Voigt Apr 10 '13 at 23:45
  • @Billy: That's what I believed (in the sense that type is a property assigned statically to objects). If that comment had not come from Johannes, I would have easily disregarded it. – Andy Prowl Apr 10 '13 at 23:47
  • @Ben: Of course, `typeid` still would not give you `int[allocated size]`. – Billy ONeal Apr 10 '13 at 23:58
  • @BillyONeal: Right -- arrays play no part in polymorphism. – Ben Voigt Apr 11 '13 at 00:00