CppQuiz一些有趣的题和分析

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
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

template <class T> void f(T &i) { std::cout << 1; }
template <> void f(const int &i) { std::cout << 2; }

int main() {
int i = 42;
const int j=42;

f(i); // match to void f(int&)
f(j); // match to void f(const int&)
}

Answer: 12

Q2

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <string>

void f(const std::string &) { std::cout << 1; }

void f(const void *) { std::cout << 2; }

int main() {
f("foo");
const char *bar = "bar";
f(bar);
}

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
2
3
4
5
6
7
8
#include <iostream>

void f(int) { std::cout << 1; }
void f(unsigned) { std::cout << 2; }

int main() {
f(-2.5);
}

**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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

struct A {
A() { std::cout << "A"; }
};
struct B {
B() { std::cout << "B"; }
};

class C {
public:
C() : a(), b() {}

private:
B b;
A a;
};

int main()
{
C();
}

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
2
3
4
5
6
7
8
#include <iostream>

int main() {
for (int i = 0; i < 3; i++)
std::cout << i;
for (int i = 0; i < 3; ++i)
std::cout << i;
}

**Answer: **012012

Q9

1
2
3
4
5
6
#include <iostream>
struct X {
X() { std::cout << "X"; }
};

int main() { X x(); }

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
2
3
4
5
6
7
#include <iostream>

int a;

int main () {
std::cout << a;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class A {
public:
A() { std::cout << "a"; }
~A() { std::cout << "A"; }
};

class B {
public:
B() { std::cout << "b"; }
~B() { std::cout << "B"; }
};

class C {
public:
C() { std::cout << "c"; }
~C() { std::cout << "C"; }
};

A a;
int main() {
C c;
B b;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <exception>

int x = 0;

class A {
public:
A() {
std::cout << 'a';
if (x++ == 0) {
throw std::exception();
}
}
~A() { std::cout << 'A'; }
};

class B {
public:
B() { std::cout << 'b'; }
~B() { std::cout << 'B'; }
A a;
};

void foo() { static B b; }

int main() {
try {
foo();
}
catch (std::exception &) {
std::cout << 'c';
foo();
}
}

Answer: acabBA

  1. First, calling foo(), creates a static B object within foo. Without an initializer, B’s default constructor will be called.
  2. 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 member a is initialized first. Being without an initializer, A’s default constructor will also be called.
  3. In A’s default constructor, it outputs a and checks x++ == 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.
  4. In the catch block, the exception is caught and outputs c.
  5. The foo() is executed again, causing B’s and A’s constructors to be called in sequence (outputting a). Since x++ was executed before, x++==0 is false this time; no exception is thrown, so A constructs successfully.
  6. Execution continues with B’s constructor, outputting b.
  7. When the program ends, destruction of B (and its members) follows the inverse order of construction.
  8. Thus, B’s destructor is called and outputs B.
  9. Lastly, A’s member destructor outputs A.

Q17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class A {
public:
A() { std::cout << 'a'; }
~A() { std::cout << 'A'; }
};

class B : public A {
public:
B() { std::cout << 'b'; }
~B() { std::cout << 'B'; }
};

int main() { B b; }

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
2
3
4
5
6
7
#include <iostream>
#include <limits>

int main() {
int i = std::numeric_limits<int>::max();
std::cout << ++i;
}

**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
2
3
4
5
6
7
#include <iostream>

int main() {
int i = 42;
int j = 1;
std::cout << i / --j;
}

Answer:The behavior is undefined.

Q27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

struct A {
virtual std::ostream &put(std::ostream &o) const {
return o << 'A';
}
};

struct B : A {
virtual std::ostream &put(std::ostream &o) const {
return o << 'B';
}
};

std::ostream &operator<<(std::ostream &o, const A &a) {
return a.put(o);
}

int main() {
B b;
std::cout << b;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

struct A {
A() { std::cout << "A"; }
A(const A &a) { std::cout << "B"; }
virtual void f() { std::cout << "C"; }
};

int main() {
A a[2];
for (auto x : a) {
x.f();
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

struct A {
A() { std::cout << "1"; }
virtual ~A() { std::cout << "2"; }
};

struct B : public A {
virtual void foo() { std::cout << "3"; }
};

int main() {
B b;
b.foo();
}

**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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct X {
X() { std::cout << "a"; }
X(const X &x) { std::cout << "b"; }
const X &operator=(const X &x) {
std::cout << "c";
return *this;
}
};

int main() {
X x;
X y(x);
X z = y;
z = x;
}

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 between X y(x) and X 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 reaching z = 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
2
3
4
5
6
7
8
#include <iostream>
#include <vector>

int main() {
std::vector<int> v1(1, 2);
std::vector<int> v2{ 1, 2 };
std::cout << v1.size() << v2.size();
}

Answer:12

v1 is initialized with one element set to 2, referring to the vector constructor:

1
2
// Effects: Constructs a vector with n copies of value, using the specified allocator.
vector(size_type n, const T& value, const Allocator& = 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class C {
public:
C(int i) : i(i) { std::cout << i; }
~C() { std::cout << i + 5; }

private:
int i;
};

int main() {
const C &c = C(1);
C(2);
}

**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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class A;

class B {
public:
B() { std::cout << "B"; }
friend B A::createB();
};

class A {
public:
A() { std::cout << "A"; }

B createB() { return B(); }
};

int main() {
A a;
B b = a.createB();
}

**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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

class B;

class A {
public:
A() { std::cout << "A"; }
B createB();
};

class B {
public:
B() { std::cout << "B"; }
friend B A::createB();
};

B A::createB(){
return B();
}

int main() {
A a;
B b = a.createB();
}

Delay the definition of mutually dependent functions until after all class definitions.

Q112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct A
{
A() { std::cout << "1"; }
A(const A&) { std::cout << "2"; }
A(A&&) { std::cout << "3"; }
};

struct B
{
A a;
B() { std::cout << "4"; }
B(const B& b) : a(b.a) { std::cout << "5"; }
B(B&& b) : a(b.a) { std::cout << "6"; }
};

int main()
{
B b1;
B b2 = std::move(b1);
}

Answer:1426

The construction order for B b1; is:

  1. Calls A constructor to construct member object a;
  2. 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
2
3
4
5
b() {
// Pseudo code, calls A's constructor to initialize object a
a.A::A();
std::cout << "4" << std::endl;
}

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, so b1.a is default initialized first, resulting in output 14.
§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 (since std::move(b1) converts the reference to b1 to an xvalue, allowing it to be moved from). In B‘s move constructor, a is initialized in the initializer list. Even though a 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, printing 2, and finally the body of B‘s move constructor prints 6. (If the concept of rvalue references being lvalues is confusing, read The Article. Search for “In widget”.)

Q116

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <utility>

int y(int &) { return 1; }
int y(int &&) { return 2; }

template <class T> int f(T &&x) { return y(x); }
template <class T> int g(T &&x) { return y(std::move(x)); }
template <class T> int h(T &&x) { return y(std::forward<T>(x)); }

int main() {
int i = 10;
std::cout << f(i) << f(20);
std::cout << g(i) << g(20);
std::cout << h(i) << h(20);
return 0;
}

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, and y(int&&) is called for both cases. Output: 22.
  • In the third example, forward<T>(x) obtains an lvalue reference when x is an lvalue reference and an rvalue reference when x is an rvalue reference, resulting in first a call to y(int&) and then a call to y(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
2
3
4
5
6
#include <iostream>

int main() {
void * p = &p;
std::cout << bool(p);
}

**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
2
unsigned char x = 12;
{ unsigned char x = x; }

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
2
3
4
5
6
7
8
9
define i32 @main() #4 {
%1 = alloca i8*, align 8
%2 = bitcast i8** %1 to i8*
store i8* %2, i8** %1, align 8
%3 = load i8*, i8** %1, align 8
%4 = icmp ne i8* %3, null
%5 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEb(%"class.std::basic_ostream"* @_ZSt4cout, i1 zeroext %4)
ret i32 0
}

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
2
3
4
5
6
7
int main() {
int a = 10;
int b = 20;
int x;
x = a, b;
std::cout << x;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

struct A {};
struct B {};

template<typename T = A> struct X;

template<> struct X<A> {
static void f() { cout << 1 << endl; }
};

template<> struct X<B> {
static void f() { cout << 2 << endl; }
};

template<template<typename T = B> class C>
void g() {
C<>::f();
}

int main() {
g<X>();
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

using namespace std;

template <class T> void f(T) {
static int i = 0;
cout << ++i;
}

int main() {
f(1);
f(1.0);
f(1);
}

**Answer: **112

1
2
3
f(1); // Specialized f(int)
f(1.1); // Specialized f(double)
f(1); // Uses the previously 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

template<typename T>
void adl(T){cout << "T";}

struct S{};

template<typename T>
void call_adl(T t)
{
adl(S());
adl(t);
}

void adl(S){cout << "S";}

int main()
{
call_adl(S());
}

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
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

class C {
public:
explicit C(int) {std::cout << "i";};
C(double) {std::cout << "d";};
};

int main() {
C c1(7);
C c2 = 7;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using namespace std;

class A
{
public:
A() { cout << "A"; }
A(const A &) { cout << "a"; }
};

class B: virtual A
{
public:
B() { cout << "B"; }
B(const B &) { cout<< "b"; }
};

class C: virtual A
{
public:
C() { cout<< "C"; }
C(const C &) { cout << "c"; }
};

class D:B,C
{
public:
D() { cout<< "D"; }
D(const D &) { cout << "d"; }
};

int main()
{
D d1;
D d2(d1);
}

**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
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <map>
using namespace std;

int main()
{
map<bool,int> mb = {{1,2},{3,4},{5,0}};
cout << mb.size();
map<int,int> mi = {{1,2},{3,4},{5,0}};
cout << mi.size();
}

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 for mb are boolean, and 1, 3, and 5 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 of mb is map. The key is bool, so the integers 1, 3, and 5 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

size_t get_size_1(int* arr)
{
return sizeof arr;
}

size_t get_size_2(int arr[])
{
return sizeof arr;
}

size_t get_size_3(int (&arr)[10])
{
return sizeof arr;
}

int main()
{
int array[10];
cout << (sizeof(array) == get_size_1(array));
cout << (sizeof(array) == get_size_2(array));
cout << (sizeof(array) == get_size_3(array));
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <limits>

int main()
{
int N[] = {0,0,0};

if ( std::numeric_limits<long int>::digits==63 and
std::numeric_limits<int>::digits==31 and
std::numeric_limits<unsigned int>::digits==32 )
{
for (long int i = -0xffffffff; i ; --i)
{
N[i] = 1;
}
}
else
{
N[1]=1;
}

std::cout << N[0] <<N [1] << N[2];
}

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 the if 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 literal 0xffffffff needs 32 digits, it can be represented as an unsigned int, but not as a signed int, and is of type unsigned 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.” Here n is 32, and we get:
2^32 - 0xffffffff = 4294967296 - 4294967295 = 1
So i is initialized to 1, and N[1] is the only element accessed in the loop. (The second time around the loop, i is 0, which evaluates to false, and the loop terminates.)

Q147

1
2
3
4
5
int main(){
int x=0; //What is wrong here??/
x=1;
std::cout<<x;
}

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
2
3
4
5
int x=0; //What is wrong here\
x=1;
// Equivalent to

int x=0; //What is wrong herex=1;

Q151

1
2
3
4
5
6
7
#include <iostream>
#include <type_traits>

int main()
{
std::cout << std::is_signed<char>::value;
}

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
2
3
4
5
6
#include <iostream>

int main() {
char* str = "X";
std::cout << str;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>

struct Foo
{
Foo() { std::cout<<"a"; }
Foo(const Foo&) { std::cout<<"b"; }
};

int main()
{
std::vector<Foo> bar(5);
}

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 constructor
explicit vector( size_type n )
which constructs a vector with n value-initialized elements. Each value-initialization calls the default Foo constructor, resulting in the output aaaaa.
The “trick” is that before C++11, std::vector had a two-parameter constructor (+ allocator), which constructed the container with n copies of the second parameter, which is defaulted to T(). So this code before C++11 would output abbbbb, because the call would be equivalent to std::vector bar(5,T()).

Q157

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <typeinfo>

struct A {};

int main()
{
std::cout<< (&typeid(A) == &typeid(A));
}

Answer: This question The program is unspecified / implementation-defined.

The result of a typeid expression is an lvalue of static type const std::type_info (18.7.1) and dynamic type const std::type_info or const name where name is an implementation-defined class publicly derived from std::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
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int i;

void f(int x) {
std::cout << x << i;
}

int main() {
i = 3;
f(i++);
}

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
2
3
4
5
6
7
8
define i32 @main() #4 {
store i32 3, i32* @i, align 4
%1 = load i32, i32* @i, align 4
%2 = add nsw i32 %1, 1
store i32 %2, i32* @i, align 4
call void @_Z1fi(i32 %1)
ret i32 0
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

struct A {
virtual void foo (int a = 1) {
std::cout << "A" << a;
}
};

struct B : A {
virtual void foo (int a = 2) {
std::cout << "B" << a;
}
};

int main () {
A *b = new B;
b->foo();
}

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.

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:CppQuiz一些有趣的题和分析
Author:LIPENGZHA
Publish Date:2016/10/24 06:15
World Count:21k Words
Link:https://en.imzlp.com/posts/10205/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!