All C++ Interview Questions

What are concepts in C++20?

A concept is a compile time condition on a type. You use it to say what a template needs.

#include <concepts>
#include <iostream>
#include <string>

template <typename T>
concept Printable = requires(T x) { std::cout << x; };

template <Printable T>
void show(const T& x) { std::cout << x << '\n'; }

template <std::integral T>
T half(T x) { return x / 2; }

int main() {
    show(42);
    show(std::string("text"));
    std::cout << half(9) << '\n';
}
Output

42
text
4

#include <concepts>

template <std::integral T>
T half(T x) { return x / 2; }

int main() {
    half(3.5);
}
Output

Wrong.cpp: In function 'int main()':
Wrong.cpp:7:9: error: no matching function for call to 'half(double)'
7 | half(3.5);
| ~~~~^~~~~
• there is 1 candidate
• candidate 1: 'template<class T> requires integral<T> T half(T)'
Wrong.cpp:4:3:
4 | T half(T x) { return x / 2; }
| ^~~~
• template argument deduction/substitution failed:
• constraints not satisfied
In file included from Wrong.cpp:1:
• /usr/local/include/c++/16.2.0/concepts: In substitution of 'template<class T> requires integral<T> T half(T) [with T = double]':
• required from here
Wrong.cpp:7:9:
7 | half(3.5);
| ~~~~^~~~~
• required for the satisfaction of 'integral<T>' [with T = double]
/usr/local/include/c++/16.2.0/concepts:109:13:
109 | concept integral = is_integral_v<_Tp>;
| ^~~~~~~~
• the expression 'is_integral_v<_Tp> [with _Tp = double]' evaluated to 'false'
/usr/local/include/c++/16.2.0/concepts:109:24:
109 | concept integral = is_integral_v<_Tp>;
| ^~~~~~~~~~~~~~~~~~

The error says clearly that double does not satisfy std::integral, instead of 50 lines of template messages. See Templates.

What is the difference between templates and macros in C++?

PointTemplateMacro
Done byCompilerPreprocessor (text)
Type safeYesNo
Scope and namespacesYesNo
DebuggingNormalHard
Side effectsNoneArguments can be evaluated twice
#include <iostream>

#define SQUARE(x) x * x

template <typename T>
constexpr T square(T x) { return x * x; }

int main() {
    int n = 3;
    std::cout << SQUARE(n + 1) << '\n';          // becomes n + 1 * n + 1 = 7: a macro bug
    std::cout << square(n + 1) << '\n';          // 16: correct
}
Output

7
16

Use constexpr, const, templates and inline functions instead of macros. Macros stay useful for include guards and conditional compiling. See Templates.

When to pass by value, reference or pointer in C++?

CaseUse
Small type (int, double, pointer)T by value
Big object, only readconst T&
Function must change the argumentT&
Argument may be missingT* (or std::optional)
Function keeps its own copyT by value, then std::move
Read only textstd::string_view
#include <iostream>
#include <string>
#include <string_view>
#include <vector>

void a(int n) { std::cout << "value " << n << '\n'; }
void b(const std::vector<int>& v) { std::cout << "const ref " << v.size() << '\n'; }
void c(int& n) { n++; }
void d(const int* p) { std::cout << (p ? "pointer set" : "no value") << '\n'; }
void e(std::string_view s) { std::cout << "view " << s << '\n'; }

int main() {
    int n = 1;
    a(n);
    b({1, 2, 3});
    c(n);
    d(&n);
    d(nullptr);
    e("text");
    std::cout << n << '\n';
}
Output

value 1
const ref 3
pointer set
no value
view text
2

See References and Pointers.

What is the explicit keyword in C++?

#include <iostream>

struct Meters {
    explicit Meters(double v) : value(v) {}
    double value;
};

void show(Meters m) { std::cout << m.value << '\n'; }

int main() {
    show(Meters(5));        // fine
    show(5);                // error: the hidden conversion is not allowed
}
Output

Implicit.cpp: In function 'int main()':
Implicit.cpp:12:10: error: could not convert '5' from 'int' to 'Meters'
12 | show(5); // error: the hidden conversion is not allowed
| ^
| |
| int
Implicit.cpp:4:14: note: explicit conversion function was not considered
4 | explicit Meters(double v) : value(v) {}
| ^~~~~~

Without explicit, show(5) would compile and quietly create a Meters. Mark single argument constructors explicit unless you really want the conversion. See Constructors.

What is perfect forwarding in C++ (std::forward)?

A function template can take any argument and pass it on without extra copies. T&& in a template is a forwarding reference, and std::forward<T>(x) keeps the original category.

#include <iostream>
#include <string>
#include <utility>

void take(const std::string&) { std::cout << "got an lvalue (copy needed)\n"; }
void take(std::string&&)      { std::cout << "got an rvalue (can move)\n"; }

template <typename T>
void forward_it(T&& x) {
    take(std::forward<T>(x));
}

int main() {
    std::string s = "text";
    forward_it(s);                      // lvalue
    forward_it(std::string("temp"));    // rvalue
}
Output

got an lvalue (copy needed)
got an rvalue (can move)

This is how std::make_unique, emplace_back and std::thread pass your arguments to constructors.

How do lambda captures work in C++?

CaptureMeaning
[x]x by value
[&x]x by reference
[=]All used variables by value
[&]All used variables by reference
[this]The current object pointer
[x = std::move(y)]Init capture (C++14): create a member
#include <iostream>
#include <memory>

int main() {
    int a = 1, b = 2;
    auto f = [a, &b] { b += a; return b; };
    std::cout << f() << ' ' << b << '\n';

    auto p = std::make_unique<int>(7);
    auto g = [q = std::move(p)] { return *q; };        // move into the lambda
    std::cout << g() << ' ' << (p == nullptr) << '\n';
}
Output

3 3
7 1

A lambda which is saved or returned must not capture local variables by reference, because the variables are destroyed. See Lambda Expressions.

What is new in C++20?

  • Concepts: named rules for templates.
  • Ranges and views: pipelines like v | views::filter(...) | views::transform(...).
  • Modules: a modern replacement for header files.
  • Coroutines: functions which can pause and continue.
  • std::format: type safe formatting.
  • Spaceship operator <=>: all comparisons with one function.
  • std::span, std::jthread, designated initializers and more.
#include <format>
#include <iostream>
#include <ranges>
#include <span>
#include <vector>

struct Point { int x; int y; };

int main() {
    Point p{.x = 1, .y = 2};                                              // designated initializers
    std::vector<int> v = {1, 2, 3, 4, 5, 6};
    auto evens = v | std::views::filter([](int n) { return n % 2 == 0; });
    std::span<const int> view{v};
    std::cout << std::format("{} {} {} {}\n", p.x + p.y, *evens.begin(), view.size(), (1 <=> 2) < 0);
}
Output

3 2 6 true

See Modern C++ Features.

What is new in C++23?

  • std::print and std::println: easy formatted output.
  • std::expected: a value or an error, without exceptions.
  • Deducing this: one member function for const and non const objects.
  • std::mdspan: a view of multidimensional data.
  • std::flat_map, std::flat_set, std::generator and more ranges features.
  • if consteval, std::unreachable, std::to_underlying and others.
#include <expected>
#include <print>
#include <utility>

enum class Color { Red = 1, Green = 2 };

std::expected<int, std::string> half(int n) {
    if (n % 2) return std::unexpected("odd number");
    return n / 2;
}

int main() {
    std::println("{}", half(10).value());
    std::println("{}", half(7).error());
    std::println("{}", std::to_underlying(Color::Green));
}
Output

5
odd number
2

All of these compile with GCC 16 and -std=c++23. See Modern C++ Features.

What is a data race in C++ and how do you prevent it?

A data race is two or more threads reading and writing the same variable at the same time with no lock. It is undefined behavior. Prevent it with:

  • std::mutex and std::lock_guard or std::scoped_lock.
  • std::atomic<T> for simple counters and flags.
  • Not sharing data: give each thread its own data.
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

int main() {
    std::atomic<int> counter{0};
    std::vector<std::thread> threads;
    for (int t = 0; t < 4; t++) {
        threads.emplace_back([&counter] { for (int i = 0; i < 50000; i++) counter++; });
    }
    for (auto& t : threads) t.join();
    std::cout << counter << '\n';
}
Output

200000

Find races with ThreadSanitizer: g++ -fsanitize=thread. See C++ Multithreading.

What is copy elision and RVO in C++?

When a function returns an object, the compiler can build it directly in the place of the caller, with no copy and no move. This is copy elision. Since C++17 it is guaranteed when you return a temporary (a prvalue). With a named local variable (NRVO) it is allowed, and most compilers do it.

#include <iostream>

struct Noisy {
    Noisy() { std::cout << "construct\n"; }
    Noisy(const Noisy&) { std::cout << "copy\n"; }
    Noisy(Noisy&&) noexcept { std::cout << "move\n"; }
};

Noisy make() { return Noisy(); }                 // guaranteed: no copy, no move

int main() {
    Noisy n = make();
}
Output

construct

So return std::move(local); is a mistake: it can stop the elision. Just write return local;. See Move Semantics.