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:
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
andnew int[10]
have typeint*
and the type ofnew int[i][10]
isint (*)[10]
—end note ] The attribute-specifier-seq in a noptr-new-declarator appertains to the associated array type.
A statically-typed language is a language (such as Java, C, or C++) where variable types are known at compile time. In most of these languages, types must be expressly indicated by the programmer; in other cases (such as OCaml), type inference allows the programmer to not indicate their variable types.
Statically typed languages: Statically typed languages are the languages like C, C++, Java, etc, In this type of language the data type of a variable is known at the compile time which means the programmer has to specify the data type of a variable at the time of its declaration.
C is strongly typed in that the variable type must be specified when declaring the variable. However, C can also be regarded as weakly typed because users can convert data types through a cast and without compiler errors.
C# — pronounced “see sharp” — is an object-oriented and statically typed computer programming language created by Microsoft for use on its . NET platform.
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With