C++ Quiz from C++ Russia 2019

Posted on by

Hi,

We’ve recently visited a fantastic C++ Russia conference in Moscow. It gathered about 600 developers from Russia and the CIS, and several international speakers, with keynotes by Nico Josuttis and two days full of great talks. And we also had time for a C++ quiz!

This year, the JetBrains C++ team ran the quiz as an evening event on the first conference day. The quiz we prepared consisted of two parts. The first one was rather easy, so the speed of replies mattered. The second one required a more thoughtful approach and a detailed answer to each question. People asked us to publish the second part so here it is. Read each question and the code sample, and see if you can get the answer right!

Question 1: Does this code compile?

template<typename> struct X {}; X() -> X<void>; X x; struct Y { static X x; };

Question 2: Does this code compile?

Answer: While line 6 is fine, line 9 should not compile. If you treat it as a definition of ‘x’, then an inline is missing. If you treat it as a declaration, then CTAD doesn’t work. However, GCC somehow manages to compile this code! See this: https://gcc.godbolt.org/z/sviRE5

template<typename> struct Outer { template<typename T> struct Inner { Inner(T); }; static inline Inner i { 239 }; }; Outer<float> o;

Question 3: Does this code compile?

Answer: Both answers are acceptable. GCC fails with an Internal Compiler Error. Clang doesn’t compile, because, per Richard Smith’s comment , CTAD for nested classes is poorly described in the standard. MSVC compiles fine.

template<typename T> struct vector { struct size_type { size_type(bool); }; vector(size_type, T); }; template<> struct vector<bool> { vector(bool, bool); }; vector v { true, false };

vector(size_type, T)

size_type

Question 4: Which line contains a compilation error?

Answer: This code should not compile. If we take a look at theconstructor, we notice the compiler infers T -> bool. Then the compiler will try to findinside the vector and will fail.

struct { int a : 3, : 4, b : 5; } a; auto &[x, y] = a; auto &[p, q, r] = a;

a

Question 5: Does this code compile?

Answer: The third line contains a compilation error. From the point of view of structured bindings,has 2 fields.

#include <type_traits> template<typename T, typename = std::enable_if_t<sizeof(T) < sizeof(int)>> constexpr bool less_than_int() { return true; } template<typename T, typename = std::enable_if_t<sizeof(T) >= sizeof(int)>> constexpr bool less_than_int() { return false; } static_assert( less_than_int<short>()); static_assert(!less_than_int<long>());

Question 6: Does this code compile?

Answer: No, it doesn’t compile. It will fail with a redefinition error, because functions that only differ in their default values for template parameters are considered equivalent.

#include <type_traits> template<typename T, typename = std::enable_if_t<sizeof(T) < sizeof(int)>> constexpr bool less_than_int() { return true; } template<typename T> constexpr bool less_than_int() { return false; } static_assert( less_than_int<short>()); static_assert(!less_than_int<long>());

less_than_int<short>()

Question 7: Does this code compile?

Answer: No, it doesn’t compile.leads to an ambiguous function call.

#include <type_traits> template<typename T, typename = std::enable_if_t<sizeof(T) > sizeof(int)>> constexpr bool greater_than_int(int) { return true; } template<typename T> constexpr bool greater_than_int(...) { return false; } static_assert(!greater_than_int<short>(0)); static_assert( greater_than_int<long>(0));

>

enable_if_t

Question 8: For each of the three pieces of code below, state if (by the standard) it doesn’t compile, has Undefined Behavior, or works fine.

Answer: No, it doesn’t compile. Theinsideis parsed as the end of the template arguments, not as greater sign.

//First piece union { int x; int y; } u {}; void test() { std::cout << u.y; } //Second piece union { struct { int x; }; struct { int y; }; } u {}; void test() { std::cout << u.y; } //Third piece union { struct { int value; } x; struct { int value; } y; } u {}; void test() { std::cout << u.y.value; }

Question 9: In C++20, the initialization of which variable (p1, p2, q1, or q2) guarantees the output order “1”, “2”?

Answer: The first piece has UB as it takes an inactive union member. The second one shouldn’t compile because anonymous structs are not described in the C++ standard, even though MSVC, GCC, and Clang support them. The third piece works, as union variants form a common initial sequence.

#include <cstdio> struct P { int x, y; }; struct Q { int x, y; Q(int x, int y) : x(x), y(y) {} }; int one() { puts("1"); return 1; } int two() { puts("2"); return 2; } P p1{one(), two()}; P p2(one(), two()); Q q1{one(), two()}; Q q2(one(), two());

{}

()

Question 10: Which of the following variables has a dangling reference (per C++20)?

Answer: In lines 13 and 15,guarantees the evaluation (and thus printing) order. In line 16, the order cannot be guaranteed. Line 14 contains the most complicated case. We can’t even provide a proper reference to the standard’s text, but the discussion in the WG21 mailing list suggests the initialization of aggregates should guarantee the order (even with).

struct A { int const& v; }; struct B { int const& v; B(int const& v) : v(v) {} }; A const& ac = A{2 + 2}; A const& ar = A(2 + 2); B const& bc = B{2 + 2}; B const& br = B(2 + 2);

()

{}

Question 11: What will be the program output? (Coroutines TS, С++20)

Answer: Line 10 is fine – for the aggregator’s fields a temporary object lifetime extension works. Lines 12 and 13 are not that successful. Line 11 is actually interesting: the initialization of aggregates withdoesn’t give the same guarantees as with

using namespace std; using namespace std::string_literals; template<typename Range> generator<char> f(Range const & range) { for (auto const & str : range) { for (char c : str) co_yield c; } } int main() { for (char c : f(array { "aba"sv, "caba"sv })) cout << c; }

const &

Question 12: What’s wrong with the traverse function? (Coroutines TS, С++20)

Answer: This program contains an Undefined Behavior, because there is a temporary object passed to the coroutine with. So, after the coroutine is suspended, this object is destroyed. Therefore, from the coroutine body there is a call to an already destroyed object.

template<typename T> struct Tree { T const & value() const; Tree const * left() const; Tree const * right() const; }; template<typename T> generator<reference_wrapper<T const>> traverse(Tree<T> const * tree) { if (!tree) co_return; for (auto t : traverse(tree->left())) co_yield t; co_yield cref(tree->value()); for (auto t : traverse(tree->right())) co_yield t; }

Question 13: Which function will be called, #1 or #2?

Answer: The traverse function works, but asymptotically is not efficient. The tree is traversed in O(n*d), where n is the number of elements and d is depth.

#include <type_traits> template<typename T> requires std::is_same<T, int>() void f(T); // #1 void f(long); // #2 void f() { f(0); }

Question 14: Which function will be called, #1 or #2?

Answer: This program doesn’t compile, because the argument of the requires-clause can’t be a call-expression.

#include <type_traits> template<typename T> requires (std::is_same<T, int>()) void f(T); // #1 void f(long); // #2 void f() { f(0); }

Question 15: Does this code contain a redefinition error?

Answer: This program doesn’t compile, because the argument of the requires-clause should be exactly bool, not merely convertible to bool.

template<typename T> requires true void f() {} template<typename T> void f() requires true {}

Answer: Clang compiles this code, but GCC does not ( https://gcc.godbolt.org/z/tDmoEU ). But it seems that Clang is right – since these functions are not equivalent, the requires-clause on the first line relates to the template heads, while the requires-clause on the second relates to the function itself. So, the template heads for them are different.

How many answers did you get right? Let us know!

Cheers,

Your ReSharper C++ Team