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';
}
42
text
4
#include <concepts>
template <std::integral T>
T half(T x) { return x / 2; }
int main() {
half(3.5);
}
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++?
| Point | Template | Macro |
|---|---|---|
| Done by | Compiler | Preprocessor (text) |
| Type safe | Yes | No |
| Scope and namespaces | Yes | No |
| Debugging | Normal | Hard |
| Side effects | None | Arguments 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
}
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++?
| Case | Use |
|---|---|
| Small type (int, double, pointer) | T by value |
| Big object, only read | const T& |
| Function must change the argument | T& |
| Argument may be missing | T* (or std::optional) |
| Function keeps its own copy | T by value, then std::move |
| Read only text | std::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';
}
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
}
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
}
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++?
| Capture | Meaning |
|---|---|
| [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';
}
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);
}
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_underlyingand 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));
}
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::mutexandstd::lock_guardorstd::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';
}
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();
}
construct
So return std::move(local); is a mistake: it can stop the elision. Just write return local;. See Move Semantics.
- « Previous
- 1
- 2
- 3