CppQuiz is a simple online quiz that you can use to test your knowledge of the C++ programming language.
It’s quite interesting; I wrote down a few questions I encountered today. I’ll add more here whenever I have time. In fact, many questions in CppQuiz can be explained by “Deep Exploration of C++ Object Model”… If you find yourself struggling with many questions, I recommend buying a copy of “Deep Exploration of C++ Object Model” and reading it thoroughly!
In addition, I will try to find relevant descriptions in the C++ standard (ISO/IEC 14882:2014) while answering questions.
According to the C++11 standard, what is the output of this program?
Q1
1 |
|
Answer: 12
Q2
1 |
|
Answer:22
Because a literal is not a std::string
object, but is a const char[]
. If you want const char[]
to match std::string
, you have to implement a user-defined conversion, which is not the optimal choice.
Ordinary string literals and UTF-8 string literals are also referred to as narrow string literals. A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration.
Q3
1 |
|
**Answer: **compilation error
Calling f
is ambiguous because -2.5
is a signed double
:
The type of a floating literal is double unless explicitly specified by a suffix. The suffixes f and F specify float; the suffixes l and L specify long double.
Converting double
to internal needs to follow Integer conversion rank, see [ISO/IEC 14882:2014 4.13].
It specifies that the conversion rank of signed integer types is equal to that of unsigned integer types:
The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type.
In this question’s case, the matching degree of the two f
functions is equal (it can convert to either, with no preference), which causes the ambiguity.
Q5
1 |
|
Answer:BA
Because when constructing C
, the initialization order of members a
and b
does not follow the order in the constructor, but rather the order in which data members are defined in the class.
Q6
1 |
|
**Answer: **012012
Q9
1 |
|
Result: does not output anything.
Because X x();
is a function prototype, not an instantiation of an object.
Changing it to X x{};
will output X
.
Q11
According to the C++11 standard, what is the output of this program?
1 |
|
Result: 0
Because objects in the static storage area without specified initialization will be zero-initialized (zero-initialized).
[ISO/IEC 14882:2014 §8.5/10]Every object of static storage duration is zero-initialized at program startup before any other initialization takes place.
Q13
1 |
|
Answer: acbBCA
First, because static objects are initialized before the first statement of the main
function (implementation-defined):
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.
In most compilers (I haven’t encountered one that isn’t), the global object a
is constructed first, then c
and b
are constructed in the order they are defined, resulting in acb
output.
When the program terminates, objects are destroyed in the reverse order of their construction, so the output is BCA
.
Q15
1 |
|
Answer: acabBA
- First, calling
foo()
, creates astatic B
object withinfoo
. Without an initializer, B’s default constructor will be called. - According to the order of object construction execution, initialize base classes (if any) and data members. Since class B has no base class and a single data member
A
, the membera
is initialized first. Being without an initializer, A’s default constructor will also be called. - In A’s default constructor, it outputs
a
and checksx++ == 0
, which is true and hence throws an exception. When an exception is thrown in a constructor, all fully constructed sub-objects (excluding union type variant members) will have their destructors called. Since there are no data members in A, the constructor ends here, and no destructors are called. - In the
catch
block, the exception is caught and outputsc
. - The
foo()
is executed again, causing B’s and A’s constructors to be called in sequence (outputtinga
). Sincex++
was executed before,x++==0
is false this time; no exception is thrown, so A constructs successfully. - Execution continues with B’s constructor, outputting
b
. - When the program ends, destruction of B (and its members) follows the inverse order of construction.
- Thus, B’s destructor is called and outputs
B
. - Lastly, A’s member destructor outputs
A
.
Q17
1 |
|
Answer: abBA
This question primarily tests the order of construction under inheritance.
Construction:
Where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
And destruction:
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes, and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).
Q25
1 |
|
**Answer: **The program is undefined.
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines and are usually adjustable by a library function. — end note]
Q26
1 |
|
Answer:The behavior is undefined.
Q27
1 |
|
Answer: B
Because polymorphism can be achieved using a pointer or reference to a base class, hence in operator<<
, calling put
on A&
will invoke B
‘s put
.
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.
As to why D d2(d1);
outputs ABDd
, it is because when you explicitly define the copy constructor for the derived class, you need to manually call the copy constructor of its base class.
If you comment out the copy constructor in D
, it will output abc
.
Q28
1 |
|
Answer: AABCBC
Because in for(auto x : a)
, the range for loop here makes a copy, transferring the elements from a
to x
one by one, thus calling the copy constructor. If you don’t want to use copies, you can pass by reference in the range for:
1 | for(auto& x : a) {} |
Q29
1 |
|
**Answer: **121
All the utilized concepts are written in the order of object construction and destruction and Don’t expect polymorphic behavior when calling virtual functions in base class constructors.
One important point is that calling a virtual function in a base class constructor will not invoke the overridden version in the derived class (because the base class constructor completes before the derived class). However, when calling the foo
method because the class construction is completed, during the execution of b.foo()
, the derived class version can be found.
Q32
1 |
|
Answer:abbc
The only important note is that X z = y;
calls the copy constructor, not operator=()
. The distinction between copy initialization and assignment operation is crucial.
The first line in main(),
X x;
, is straightforward; it calls the default constructor.
The next two lines are the heart of the question: The difference betweenX y(x)
andX z = y
is not just that the first calls the copy constructor and the second calls the copy assignment operator. The distinction lies in direct initialization (§8.5.15 in the standard) versus copy initialization (§8.5.14).
§8.5.16 states: “If the initialization is direct-initialization, or if it is copy-initialization where the (…) source type is the same class as (…) the class of the destination, constructors are considered.” So both our cases utilize the copy constructor.
Only when reachingz = x;
do we see an actual assignment using the assignment operator.
See http://stackoverflow.com/#1051468 for a more detailed discussion of direct versus copy initialization.
Q35
1 |
|
Answer:12
v1
is initialized with one element set to 2, referring to the vector constructor:
1 | // Effects: Constructs a vector with n copies of value, using the specified allocator. |
v2
uses an initializer list to construct the vector object, resulting in a count equal to the number of elements in the initializer list.
Q49
1 |
|
**Answer: **1276
This is because a const reference extends the lifetime of a temporary object within the scope of that reference, so C(1)
will not call a destructor immediately after the expression, but rather when it leaves the current block.
Q52
1 |
|
**Answer: ** compilation error
Even though there’s a forward declaration of A
before B
is defined, the friend function A::createB()
cannot be determined whether it exists in A
at this time, thus leading to a compilation error.
One way to solve it is to look at the code:
1 |
|
Delay the definition of mutually dependent functions until after all class definitions.
Q112
1 | struct A |
Answer:1426
The construction order for B b1;
is:
- Calls
A
constructor to construct member objecta
; - Calls
B
‘s default constructor.
When a class has a default constructor and one or more member class objects, the class expands its existing default constructor so the necessary member class object constructor is called before the user code in the class’s default constructor.
Thus, the above code for B
‘s default constructor expands to:
1 | b() { |
If multiple member objects require construction, C++ requires calling each member class object’s default constructor in the order of member objects’ declaration in the class.
More content can be found in “Deep Exploration of C++ Object Model” P43.
First,
b1
is default initialized. All members are initialized before the body of the constructor, sob1.a
is default initialized first, resulting in output14
.
§12.6.2¶8 in the standard: “In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (…) then if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in §8.5 (…) otherwise, the entity is default-initialized.”
Then,b2
is initialized with the move constructor (sincestd::move(b1)
converts the reference tob1
to an xvalue, allowing it to be moved from). InB
‘s move constructor,a
is initialized in the initializer list. Even thougha
is an rvalue reference (and bound to an rvalue),a
itself is an lvalue, and cannot be moved from.b2.a
is then copy initialized, printing2
, and finally the body ofB
‘s move constructor prints6
. (If the concept of rvalue references being lvalues is confusing, read The Article. Search for “In widget”.)
Q116
1 |
|
The T&& in the templated functions do not necessarily denote an rvalue reference; it depends on the type that is used to instantiate the template. If instantiated with an lvalue, it collapses to an lvalue reference; if instantiated with an rvalue, it collapses to an rvalue reference. See note [1].
Scott Meyers has written a very good article about this, where he introduces the concept of “universal references” (note that this is not C++ standard wording) http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers.
In this example, all three functions are called once with an lvalue and once with an rvalue. In all cases, calling with an lvalue (i
) collapses T&& x
to T& x
(an lvalue reference), and calling with an rvalue (20
) collapses T&& x
to T&& x
(an rvalue reference). Inside the functions, x
itself is always an lvalue, regardless of whether its type is an rvalue reference or an lvalue reference.
- In the first example,
y(int&)
is called for both cases. Output:11
. - In the second example,
move(x)
obtains an rvalue reference, andy(int&&)
is called for both cases. Output:22
. - In the third example,
forward<T>(x)
obtains an lvalue reference whenx
is an lvalue reference and an rvalue reference whenx
is an rvalue reference, resulting in first a call toy(int&)
and then a call toy(int&&)
. Output:12
.
Note [1]: §8.3.2¶6 in the standard: “If a (…) type template-parameter (§14.3.1) (…) denotes a type TR that is a reference to a type T, an attempt to create the type ‘lvalue reference to cv TR’ creates the type ‘lvalue reference to T’, while an attempt to create the type ‘rvalue reference to cv TR’ creates the type TR.” The example at the end of that paragraph is worth a look.
Note from the contributor: This illustrates Scott Meyers’s advice to use std::forward
for universal references and std::move
for rvalue references.
Q119
1 |
|
**Answer: **1
Because assigning an object to itself at declaration is permissible (although it will have an indeterminate value).
The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below.
1 | unsigned char x = 12; |
Here the second
x
is initialized with its own (indeterminate) value.
In this question, p
is assigned a pointer pointing to p
, and it can be assured that it is not NULL
. Thus, converting it to bool
results in true
.
[ISO/IEC 14882:2014 §4.12]A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.
We can take a look at the intermediate code to analyze how the compiler implements it:
1 | define i32 @main() #4 { |
As seen:
1 | %4 = icmp ne i8* %3, null |
This line checks whether %3
is not equal to null
; hence a ne
(not equal) comparison is performed, confirming that %3
, which has an actual address, is not null
. Therefore, the resulting boolean value is true
.
Q120
1 | int main() { |
Output: 10
Because the comma operator has the lowest precedence, resulting in the assignment operation being executed first, followed by the comma operator.
C++ operator precedence can be viewed in this article: C++ keywords and operator precedence quick reference
Q124
1 |
|
Answer:2
Note: C<>::f()
calls the version with the default template argument, which corresponds to C<X<B>>::f()
, hence the output is 2
.
A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter.
Q125
1 |
|
**Answer: **112
1 | f(1); // Specialized f(int) |
This results in two versions of f
, each having their own static object i
, hence the output will be 112
.
Q130
1 |
|
Answer:TS
This happens because the first call of adl(S())
in call_adl
is a Non-dependent name
, following ordinary name lookup rules. At this point, the only version of adl
present is adl(T)
, and adl(S)
has not been defined yet, thus it won’t be included in the candidate set.
However, for the second call adl(t)
which depends on the template parameter, the name resolution is postponed until instantiation at call_adl(S())
, at which point adl(S)
is already declared and defined, thus it gets matched.
When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1, 3.4.2) are applied for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known (14.6.2).
Q131
1 |
|
Answer:id
An explicit constructor is only called in direct initialization.
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value-initialization (8.5).
Conversion constructor:
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.
C c1(7)
is direct initialization, while C c2=7;
is copy initialization. They should generally be equivalent, but in the above code, the difference exists.
As well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.
While copy initialization:
As well as in argument passing, function return, throwing an exception (15.1), handling an exception(15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [Note: Copy-initialization may invoke a move (12.8). — end note]
Explicit constructors are only called in direct initialization, hence in C c2=7;
, the constructor C(int)
won’t be a candidate, allowing only the match to C(double)
.
Q133
1 |
|
**Answer: **ABCDABCd
This also utilizes the order of object construction and destruction. The only potential confusion in this code is how many times A
is constructed. Because B
and C
use virtual inheritance, it only gets constructed once. Since B
appears first in D
, the construction proceeds in left-to-right order with a depth-first strategy: first calling A
‘s constructor, followed by B
, then C
, and finally executing D
‘s constructor.
Q135
1 |
|
Answer:13
First, you need to understand that a map can only store unique keys. Additionally, the values 1/3/5 are all converted to the bool type, which results in true… At this point, mb[true]
is 2.
std::map
stores values based on a unique key. The keys formb
are boolean, and1
,3
, and5
all evaluate to the same key,true
.
§23.4.4.1¶1 in the standard:
“A map is an associative container that supports unique keys (contains at most one of each key value).”
The type ofmb
ismap
. The key is bool, so the integers1
,3
, and5
used for initialization are first converted to bool, and they all evaluate to true.
§4.12¶1 in the standard:
“A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.”### Q140
1 |
|
Answer:001
Because get_size_1 and get_size_2 only receive an int pointer, the size obtained from sizeof is just the size of an int*. In contrast, get_size_3 receives a ten-element int array, so the sizeof operation yields the size of the ten-element int array.
Q144
1 |
|
Answer:010
The result of std::numeric_limits<T>::digits
indicates the data bit size of T (sizeof(T)*CHAR_BIT
, excluding the sign bit).
Moreover, the C++ standard only specifies the minimum range
that built-in data types can represent, as shown in the following table:
Type | Meaning | Minimum Size |
---|---|---|
bool | Boolean type | Undefined |
char | Character | 8 bits |
wchar_t | Wide character | 16 bits |
char16_t | Unicode character | 16 bits |
char32_t | Unicode character | 32 bits |
short | Short type | 16 bits |
int | Integer | 16 bits |
long | Long type | 32 bits |
long long | Long type | 64 bits |
float | Single precision float | 6 significant digits |
double | Double precision float | 10 significant digits |
long double | Extended precision float | 10 significant digits |
No need to elaborate on the else part; the main point is to focus on the if condition.
The key concept is that the negative of an unsigned number is obtained by subtracting its value from $2^n$, i.e.:
-0xffffffff
= $2^n - \sum_{x=0}^{31}2^i$ = 1
As the
else
part of the branch is obvious, we concentrate on theif
part and make the assumptions present in the condition.
§2.14.2 in the standard: “The type of an integer literal is the first of the corresponding list in Table 6.” [Table 6: int, unsigned int, long int, unsigned long int … for hexadecimal literals – end Table] in which its value can be represented.”
Since the literal0xffffffff
needs 32 digits, it can be represented as anunsigned int
, but not as a signedint
, and is of typeunsigned int
. But what happens with the negative of an unsigned integer?
§5.3.1 in the standard: “The negative of an unsigned quantity is computed by subtracting its value from 2^n, where n is the number of bits in the promoted operand.” Heren
is32
, and we get:2^32 - 0xffffffff = 4294967296 - 4294967295 = 1
Soi
is initialized to1
, andN[1]
is the only element accessed in the loop. (The second time around the loop,i
is0
, which evaluates to false, and the loop terminates.)
Q147
1 | int main(){ |
Answer:0
Because ??/ is a Trigraph sequence that represents \
. The effect of \ is to change the next physical line into a logical line.
Each instance of a backslash character (
\
) immediately followed by a newline character is deleted, splicing physical source lines to form logical source lines.
So, the above code is actually equivalent to:
1 | int x=0; //What is wrong here\ |
Q151
1 |
|
The C++ standard specifies that whether char is unsigned or signed is implementation-defined behavior.
. It is implementation-defined whether a char object can hold negative values.
Q153
1 |
|
A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration.
In C++11, converting const char* to char* is illegal.
ISO C++11 does not allow conversion from string literal to ‘char *’.
The solution is:
1 | const char* str="x"; |
Q158
1 |
|
Answer:aaaaa
vector<Foo> bar(5)
where bar(5)
does not pass 5 to the Foo constructor; it specifies the number of elements in the vector container.
When creating an ordered container
, you can explicitly specify the container size
and an (optional) element initializer.
The container size
can be a constant
or non-constant expression
, and the element initializer must be a value that can initialize the object of its element type.
If you want to provide an initialization when specifying the container size, you can use:
1 | std::vector<Foo> initTenFooObj(10,Foo()); |
Since C++11 (§23.3.6.2¶3 in the standard),
std::vector
has a one-parameter constructorexplicit vector( size_type n )
which constructs a vector withn
value-initialized elements. Each value-initialization calls the defaultFoo
constructor, resulting in the outputaaaaa
.
The “trick” is that before C++11,std::vector
had a two-parameter constructor (+ allocator), which constructed the container withn
copies of the second parameter, which is defaulted toT()
. So this code before C++11 would outputabbbbb
, because the call would be equivalent tostd::vector bar(5,T())
.
Q157
1 |
|
Answer: This question The program is unspecified / implementation-defined.
The result of a
typeid
expression is an lvalue of static typeconst std::type_info
(18.7.1) and dynamic typeconst std::type_info
or const name where name is an implementation-defined class publicly derived fromstd::type_info
which preserves the behavior described in 18.7.1
And the unary operator &
is:
The result of the unary
&
operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id.
Two typeid
operations produce two pointers, and comparing the two pointers compares the values they store:
Comparing pointers is defined as follows: Two pointers compare equal if they are both null, both point to the same function, or both represent the same address (3.9.2), otherwise they compare unequal.
There is no guarantee that the same std::type_info
instance will be referenced by all evaluation references of typeid expressions of the same type.
So, this is implementation-defined.
Q159
1 |
|
Answer: 34
The C++ standard states that the side effects of argument evaluations are sequenced before the function is entered.
[ISO/IEC 14882:2014 §5.2.3.8] All side effects of argument evaluations are sequenced before the function is entered.
We can also look at the implementation of this feature from the viewpoint of IR code as follows:
1 | define i32 @main() #4 { |
We can see that when we call f(i++), the compiler first increments i and then passes the pre-increment value to f. But when entering function f, since the static object i has already been modified in the main function, the values output in function f are 3 and 4.
Q160
1 |
|
Answer: B1
Because overriding the function does not replace the default arguments, the default argument still remains 1 in class B.
A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides.