- Preprocessor Directives
- Constants
- Literals
- Initialization
- Type Inference
- Control-Flow Enhancements
- Lambdas
- Other Interesting Parts
- Object-Oriented
- In-Class Member Initializers [C++11]
- Inline Static Member Initialization [C++17]
- Delegating Constructors [C++11]
- Inherited Constructors [C++11]
- Move Constructors [C++11]
- Explicit Conversion Operators [C++11]
- Explicit virtual Function Override [C++11]
- final [C++11]
- Operator <=> (Three-Way Comparator) [C++20]
- Defaulted Special Member Functions [C++11]
- Deleted Special Member Functions [C++11]
- Ref-Qualifiers [C++11]
- Explicit Object Member Functions [C++23]
- Strongly Typed Enumerations [C++11]
- Multidimensional Subscript Operator [C++23]
- Templates
- Deprecated Features
Since the additions in this section are very few, most directives are grouped here.
#if expression
#ifdef id // equivalent to: #if defined(id)
#ifndef id // equivalent to: #if !defined(id)
#elif expression
#else
#endif
// New in C++23
#elifdef id // equivalent to: #elif defined(id)
#elifndef id // equivalent to: #elif !defined(id)#define id [expressions]
#define id(params) [expressions]
#define id(params, ...) [expressions] // [C++11] __VA_OPT__() and __VA_ARGS__ can manage the ellipsis
#define id(...) [expressions] // [C++11] __VA_OPT__() and __VA_ARGS__ can manage the ellipsis
#undef id
// __VA_ARGS__ is not always available before C++11.
// __VA_OPT__ is available since C++20.
// Example using __VA_ARGS__ and __VA_OPT__ (C++20)
#define LOG(msg, ...) printf("[" __FILE__ ":%s:%d] " msg, __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)Operators
- Operator Stringification
#: Converts a token to a string. - Operator Token-Pasting
##: Concatenates two tokens.
#define kStringify(x) #x
#define kConcat(x, y) x##y
int num1 = 5;
int num2 = 10;
int result = kConcat(num, 1) + kConcat(num, 2);
printf("%s = %d\n", kStringify(result), result); // result = 15#include <header> // global header
#include "header" // local header
#if __has_include(<header>) // global header check [C++17]
...
#elif __has_include("header") // local header check [C++17]
...
#endif
#if defined(__has_include)
#if __has_include(<optional>)
#include <optional>
#endif
#endif#warning "message" // [C++23]
#error "message" // [C++98]Implementation-Defined Behavior Control:
#pragma name // Usually 'name' is compiler-specific
_Pragma(string) // Allows using pragma inside macros [C++11]
#pragma once
#pragma pack(push) // Save current alignment
#pragma pack(push, value) // Save current alignment and set to 'value'
#pragma pack(pop) // Restore last alignment
#pragma pack(value) // Set alignment to 'value'
#pragma pack() // Restore default alignmentNot all compilers define the same macros, so their availability may vary.
List of some of non-standard predefined macros C++ Macros
__cplusplus
__STDC_HOSTED__ [C++11]
__LINE__
__FILE__
__DATE__
__TIME__
__func__ Current function name. GCC, Clang, MSVC 2012+, Intel
__FUNCTION__ Current function name. Non-standard
__FUNCDNAME__ Current function name. Non-standard
__PRETTY_FUNCTION__ Current function name. Non-standard [GCC]
__FUNCSIG__ Current function name. Non-standard [MSVC]
__STDCPP_DEFAULT_NEW_ALIGNMENT__ [C++17]
__STDCPP_BFLOAT16_T__ [C++23]
__STDCPP_FLOAT16_T__ [C++23]
__STDCPP_FLOAT32_T__ [C++23]
__STDCPP_FLOAT64_T__ [C++23]
__STDCPP_FLOAT128_T__ [C++23]
__STDC__
__STDC_VERSION__ [C++11]
__STDC_ISO_10646__ [C++11]
__STDC_MB_MIGHT_NEQ_WC__ [C++11]
__STDCPP_THREADS__ [C++11]
__STDCPP_STRICT_POINTER_SAFETY__ [C++11]
- The type of
nullptrisstd::nullptr_t. - It replaces the macro
NULLwith a more meaningful, type-safe keyword. nullptris not0,0x0,((void*)0), etc.
Benefits:
- Improves code readability and type safety.
Example;
void foo(char *);
void foo(int);
void foo(std::nullptr_t); // Since C++11 we can detect nullptr
foo(NULL); // Ambiguous: could call either overload
foo(nullptr); // Calls foo(std::nullptr_t) if defined, if not calls foo(char *)-
constexprvariables are evaluated at compile time and cannot be modified.- Built-in types:
constexprimpliesconst(bool,int,float,uint64_t, ...). - Pointers:
constexprimplies that the pointer itself is constantint * constbut NOTconst int *.
- Built-in types:
-
constexprfunctions can be executed at compile time if all arguments are constant expressions.
- C++11: Limited to a single
returnexpression and cannot contain loops or branches. - C++14: Allows conditionals (
if,switch), loops, and multiple statements. Class types inconstexprfunctions can have mutable members. - C++17: Introduces
constexprlambdas. - C++20: Allows
constexprdestructors and virtual functions, and the use of exceptions inconstexprcontexts. - C++20: Allows
constexprdynamic allocations.
// C++11 constexpr function (single return only)
constexpr int add(int a, int b) {
return a + b;
}
// C++14 constexpr function (multiple statements, loops)
constexpr int factorial(int n) {
if (n <= 1) return 1;
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
// C++17 constexpr lambda
auto identity = [](int n) constexpr { return n; };
// C++20 constexpr virtual function
struct A {
virtual int foo() const = 0;
constexpr virtual ~A() { ... }
};
struct B : public A {
constexpr virtual int foo() const override {
return 42;
}
constexpr virtual ~B() { ... }
};
// C++20 constexpr Dynamic allocations.
struct Node {
int value;
Node* next;
constexpr Node(int v, Node* n = nullptr) : value(v), next(n) {}
};
constexpr Node* build_list(int n) {
if (n <= 0) return nullptr;
Node* head = new Node(n);
Node* current = head;
for (int i = n - 1; i > 0; --i) {
current->next = new Node(i);
current = current->next;
}
return head;
}
constexpr Node* my_list = build_list(5); // Constructed at compile timeBenefits:
- Enables compile-time computation and checking.
- Facilitates optimizations and metaprogramming.
Special cases:
// In this case, const is irrelevant. It only adds some semantic value.
constexpr char kStrA[] = "Hola";
constexpr const char kStrB[] = "Hola";
// Remember the address of a variable inside a function is unknown at compile time
int value = 0;
int
main() {
constexpr int * constexprPtr = &value; // We can NOT modify the pointer, only the value. It is equal to 'constexpr int * const'
constexpr const int * constexprConstPtr = &value; // We can NOT modify either the pointer or the value. It is equal to 'constexpr const int * const'
constexpr int * const constexprPtrConst = &value; // We can NOT modify the pointer, only the value. The second const is redundant!
constexpr const int * const constexprConstPtrConst = &value; // We can NOT modify either the pointer or the value. The second const is redundant!
int * ptr = &value; // We can modify the pointer and the value
const int * constPtr = &value; // We can modify the pointer but NOT the value
int * const ptrConst = &value; // We can NOT modify the pointer, only the value
const int * const constPtrConst = &value; // We can NOT modify either the pointer or the value
constexpr int & constexprRef = value;
constexpr const int & constexprConstRef = value;
// This is not valid in C++
//constexpr int & const constexprRefConst = value;
//constexpr const int & const constexprConstRefConst = value;
int & ref = value;
const int & constRef = value;
// This is not valid in C++
//int & const refConst = value;
//const int & const constRefConst = value;
//---------------------------------
// constexpr pointers
//---------------------------------
*constexprPtr = 1;
//*constexprConstPtr = 2; // [WRONG] The value is const
*constexprPtrConst = 3;
//*constexprConstPtrConst = 4; // [WRONG] The value is const
//++constexprPtr; // [WRONG] The pointer is const
//++constexprConstPtr; // [WRONG] The pointer is const
//++constexprPtrConst; // [WRONG] The pointer is const
//++constexprConstPtrConst; // [WRONG] The pointer is const
//---------------------------------
// normal pointers
//---------------------------------
*ptr = 5;
//*constPtr = 6; // [WRONG] The value is const
*ptrConst = 7;
//*constPtrConst = 8; // [WRONG] The value is const
++ptr;
++constPtr;
//++ptrConst; // [WRONG] The pointer is const
//++constPtrConst; // [WRONG] The pointer is const
//---------------------------------
// constexpr references
//---------------------------------
constexprRef = 9;
//constexprConstRef = 10; // [WRONG] The value is const
++constexprRef; // Valid but don't do this at home.
//++constexprConstRef; // [WRONG] The reference is const
//---------------------------------
// normal references
//---------------------------------
ref = 13;
//constRef = 14; // [WRONG] The value is const
++ref;
//++constRef; // [WRONG] The reference is const
return 0;
}- Ensures that a variable with static storage duration is initialized at compile time (i.e., constant initialization).
- The variable remains mutable at runtime.
- It can be used in global objects or objects declared with static or extern.
Benefits:
- Avoids the static-initialization-order fiasco.
- Enables stronger compile-time guarantees.
- Improves code safety by avoiding undefined behavior.
- Facilitates static analysis and optimizations.
Example:
constexpr int ce = 3;
constinit int ci = ce; // OK: x is a constexpr
//constinit int error = ci; // Error: ci is not usable in constant expressions
void foo() {
ce++; // Error: ce is constexpr
ci++; // OK: ci is constinit (and mutable)
}- Defines an immediate function: it must be evaluated at compile time.
- Cannot be called at runtime.
Benefits:
- Improves performance by pre-computing values.
- Enables compile-time checks and optimizations.
- Enforces compile-time computation for functions that must produce constant expressions.
Example;
consteval int add(int a, int b) {
return a + b;
}
constinit int ci = add(2, 3);
constexpr int ce = add(2, 3);
const int c = add(ce, 3);
int i = add(c, 3);
//--
int e1 = add(ce, ce);
int e2 = add(ci, 1); // Error: The value of ci is not known at compile time
int e3 = add(i, 1); // Error: The value of i is not known at compile time| Keyword | Applies To | Evaluation Time |
|---|---|---|
constexpr [C++11] |
Functions and variables | Compile‐time or runtime |
consteval [C++20] |
Functions | Always compile‐time |
constinit [C++20] |
Variables | N/A (variable‐level) |
Detects whether the current evaluation context is a constant expression.
Example;
constexpr double
power(double b, int x) {
if (std::is_constant_evaluated()) {
// Compile-time path
double result = 1;
for (int i = 0; i < x; ++i) {
result *= b;
}
return result;
}
else {
// Runtime path
return std::pow(b, double(x));
}
}Prefix 0b or 0B followed by one or more binary digits (0 or 1).
Example;
int a = 0b0001;
unsigned int b = 0B0010;The single-quote character (') may be used arbitrarily as a digit separator in numeric literals to improve readability.
Useful to make numbers more 'human readable'.
Example;
int bin = 0b0000'0011'1110'1000;
int oct = 0'17'50;
int dec = 1'23'456'7890;
int hex = 0x03'E8;
float flt = 0.1234'5678f;
double dbl = 0.12'34'56'78;0b / 0B Binary 0b1001 / 0B1001 [C++14]
0 Octal 0123
0x / 0X Hexadecimal 0x123 / 0X123
u / U unsigned 123u / 123U
l / L long 123l / 123L
lu / ul unsigned long 123ul / 123lu
LU / UL unsigned long 123UL / 123LU
ll / LL long long 123ll / 123LL
llu / ull unsigned long long 123ull / 123llu [C++11] (officially)
LLU / ULL unsigned long long 123ULL / 123LLU [C++11] (officially)
z / Z signed size_t 123z / 123Z [C++23]
uz / UZ size_t 123uz / 123UZ [C++23]
f / F float 2f, 2.0f
l / L long double 2.0l / 2.0L
e / E Exponent 2e2 / 2E2 == 2 * 10^2 == 200
0x / 0X Hexadecimal float
p / P Binary exponent for hex float 0x2.1p0 = (2 + 1/16) * 2^0 = 2.0625
Hexadecimal float regex:
[+-]? 0 [xX] ( [0-9a-fA-F]* . [0-9a-fA-F]+ | [0-9a-fA-F]+ .? ) [pP] [+-]? [0-9]+ [flL]?
Examples:
0x2p1f = 2 * 2^1 = 4.0f
-0x2.1p0 = (2 + 1/16) * 2^0 = -2.0625
0x1.0p10L = 1024.0L
0x0.8p-1 = 0.25L wchar_t L'a'
u8 char8_t (UTF-8) u8'a' Range [0x0, 0x7F] [C++17]
u char16_t (UTF-16) u'a' Range [0x0, 0xFFFF] [C++11]
U char32_t (UTF-32) U'a' Range [0x0, 0xFFFFFFFF] [C++11]
Note: Some compilers may not fully support all Unicode character literal prefixes.
Example;
using namespace std::literals::string_literals; [C++14]
using namespace std::literals::string_view_literals; [C++17]
std::string str0 = "str"s; // C++14. Defined in std::literals::string_literals
std::string_view str1 = "str"sv; // C++17. Defined in std::literals::string_view_literals
wchar_t str2 = L'a';
std::wstring str3 = L"str";
char8_t str4 = u8'a'; // C++20 (UTF-8)
std::u8string str5 = u8"str"; // C++20 (UTF-8)
char16_t str6 = u'a'; // C++11 (UTF-16)
std::u16string str7 = u"str"; // C++11 (UTF-16)
char32_t str8 = U'a'; // C++11 (UTF-32)
std::u32string str9 = U"str"; // C++11 (UTF-32)R"delimiter( raw characters )delimiter"
- The delimiter can be any sequence of characters (except
"(or)"). - Useful for embedding strings that contain backslashes, quotes, or multiple lines.
Example;
const char *html = R"HTML(
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
)HTML";Defined in the inline namespace std::literals::complex_literals. Creates a std::complex<T> representing a pure imaginary value.
Suffixes:
if→std::complex<float>i→std::complex<double>il→std::complex<long double>
Example;
using namespace std::literals::complex_literals;
auto a = 5if; // std::complex<float>(0, 5)
auto b = 3.0i; // std::complex<double>(0, 3.0)Defined in the inline namespace std::literals::chrono_literals. [C++14]
Suffixes:
h // std::chrono::hours
min // std::chrono::minutes
s // std::chrono::seconds
ms // std::chrono::milliseconds
us // std::chrono::microseconds
ns // std::chrono::nanoseconds
y // std::chrono::year (since C++20)
d // std::chrono::day (since C++20)
Example;
using namespace std::literals::chrono_literals;
auto timeout = 5min + 30s; // 5 minutes + 30 seconds
auto year = 2023y; // std::chrono::year{2023}
auto days = 15d; // std::chrono::day{15}Allows users to define custom suffixes and conversion rules. All user-defined literal operators must begin with an underscore _. Standard library literal suffixes never start with an underscore.
Allowed parameter lists for literal operator functions:
// Numeric
( unsigned long long int )
( long double )
// Characters
( char )
( wchar_t )
( char8_t ) // C++20
( char16_t )
( char32_t )
// Strings
( const char * )
( const char * , size_t )
( const wchar_t * , size_t )
( const char8_t * , size_t ) // C++20
( const char16_t * , size_t )
( const char32_t * , size_t )Example;
constexpr float
operator "" _deg (long double deg) {
return float(deg * 3.141592 / 180.0);
}
constexpr float
operator "" _deg (unsigned long long int deg) {
return float(deg * 3.141592 / 180.0);
}
// Usage:
sprite.Rotate(180_deg); // Rotate receives radians as parameterstd::initializer_list<T>is a lightweight container representing a sequence of objects of typeT.- It is primarily used to initialize containers and aggregate types in a concise way.
- An
initializer_listis immutable (its elements cannot be modified).
Benefits:
- Improves code readability and conciseness.
- Can be more efficient than manually inserting elements.
- Enables uniform initialization syntax.
- Copying an initializer_list doesn't copy the actual elements, only references them.
Example;
#include <initializer_list>
#include <vector>
#include <iostream>
std::initializer_list<int> values = {1, 2, 3, 4, 5};
std::vector<float> vec = {1, 2.0, 3.0f}; // Automatic conversion
void printValues(std::initializer_list<int> values) {
for (auto it = values.begin(); it != values.end(); ++it) {
std::cout << *it << " ";
}
}- Provides a consistent syntax for initializing objects using braces
{}. - Works for built-in types, aggregates, and user-defined types.
- Prevents narrowing conversions (the type inside braces must be representable by the target).
Example;
int x { 42 };
double y { 3.14 };
int z { 3.14 }; // Error: narrowing conversion
int w { }; // Zero-initialized
int *p { }; // Initialized to nullptr
int arr[] { 1, 2, 3, 4 };
struct Person {
std::string name;
int age;
};
Person p1 { "Carlos", 10 };
std::vector<float> v {1, 2, 3};- C++17 relaxes some aggregate rules, allowing default members and inheritance in aggregates.
Example: Class that remains an aggregate despite having base classes (under certain conditions)
struct Base {
int a;
};
struct Derived : Base {
int b;
};
Derived d{ {1}, 2 }; // Inicializa Base::a = 1, Derived::b = 2- This is a C99 standard feature that was not included in C++.
- Prior to C++20, aggregate initialization relied on the order of members in the class or struct definition. However, this could lead to errors if the order changed or if some members were added or removed.
Example;
struct Point {
int x;
int y;
int z;
};
Point p1 { .x = 10, .y = 20, .z = 30 };
Point p2 { .x = 10, .z = 30 }; // .y is zero-initialized
Point p3 { .z = 30, .x = 10 }; // Error: Order is importantNote: The fact that order matters makes it less useful.
Type inference reduces boilerplate by letting the compiler deduce variable or function return types from context.
- Replaces explicit type declarations, especially useful with long or complex types.
- Improves code readability and reduces verbosity.
Example;
auto it = std::find(begin(vec), end(vec), 4);
// Instead of:
std::vector<int>::iterator it = std::find(begin(vec), end(vec), 4);
// C++14: Deduction for return type (not arguments):
template<typename A, typename B>
auto add(A a, B b) {
return a + b;
}
// C++20: auto in function parameters (deduction for arguments):
auto
multiply(auto a, auto b) {
return a * b;
}- Yields the type of an expression without evaluating it.
- Especially useful to deduce types in template code or to declare variables based on expressions.
Example;
auto a = 2.3f;
auto b = 1;
decltype(a + b) c; // c has the same type as (a + b), which is float
if (std::is_same<decltype(c), int>::value)
std::cout << "c is int\n";
if (std::is_same<decltype(c), float>::value)
std::cout << "c is float\n";- Deduces types while preserving references and const/volatile qualifiers.
- Useful when you want exactly the type returned by an expression.
Example;
int y = 0;
const int & y2 = y;
const auto y3 = y2; // y3 is const int
decltype(auto) y4 = y2; // y4 is const int&- Specifies a function's return type after the parameter list using
->. - Enables return type deduction based on parameters (useful for templates).
Example;
template<typename A, typename B>
auto add(A a, B b) -> decltype(a + b) {
return a + b;
}- Allows unpacking tuple-like or aggregate objects directly into named variables.
Example:
#include <tuple>
std::tuple<int, double, std::string> getData() {
return {42, 3.14, "hello"};
}
auto [i, d, s] = getData(); // i == 42, d == 3.14, s == "hello"
//--
struct Punto { int x, y; };
Punto p{10, 20};
auto [px, py] = p; // px == 10, py == 20std::vector<int> vec = {1, 2, 3, 4, 5};
for(auto &value : vec) {
value += 3;
}- Introduces an initializer before the range; helpful to manage loop index or temporary variables.
std::vector<std::string> vec = {"apple", "banana", "cherry"};
for(size_t index = 0; const auto &value : vec) {
printf("%zu: %s\n", index++, value.c_str());
}- Declares a variable inside the
ifstatement, reducing its scope.
if(auto it = std::find(begin(vec), end(vec), 4); it != end(vec)) {
*it = -4;
}- Evaluates the condition at compile time if all conditions and branches are constexpr.
- Unused branches are not compiled.
- Used for constant expressions within templates or compile-time calculations.
- Suitable for conditions based on known values at compile time.
- Can be used for template specialization based on constant checks.
- Limited by the constexpr restrictions.
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>)
return *t;
else
return t;
}- Evaluates to true only when the context is a constant expression.
- Notice that braces are mandatory: `if consteval { } else { }```
// Used by ipow
consteval uint64_t ipow_ct(uint64_t base, uint8_t exp) {
if (base == 0)
return 0;
uint64_t result = 1;
while (exp) {
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
}
return result;
}
constexpr uint64_t ipow(uint64_t base, uint8_t exp) {
if consteval {
// Compile-time path
return ipow_ct(base, exp);
}
// use runtime evaluation
return std::pow(base, exp);
}Note: In C++20, you can use std::is_constant_evaluated() instead of if consteval to differentiate compile-time versus runtime.
- Declares a variable inside the
switchstatement, limiting its scope.
enum class Status { Init, Run };
switch(auto status = GetStatus(); status) {
case Status::Init:
...
break;
case Status::Run:
...
break;
}- Provides a concise way to create anonymous function objects.
Structure:
[captureList](parameters) mutable -> returnType { body }Example:
auto lambda = [foo](int a, int b) -> decltype(a + b) {
return foo + a + b;
};
// The lambda code generated by the compiler would be something like this:
class __RandomName {
public:
__RandomName(int _foo) : foo{_foo} { }
inline int operator()(int a, int b) const {
return foo + a + b;
}
private:
int foo;
};- capture-list (optional):
[&]: capture all visible variables by reference.[=]: capture all visible variables by value.[&foo, bar]: capturefooby reference andbarby value.
- (parameters) (optional): Lamba parameters.
- mutable (optional): By default, the generated
operator()isconst. To modify captured variables, addmutableafter parameters. - return-type (optional, deduced if not specified).
- body: the function body.
Example:
auto lambda = [foo = 0](int a, int b) mutable {
++foo; // Modifies the captured local copy of foo
return foo + a + b;
};Note: Lambda types are unique and can only be stored in an auto or a templated type (e.g., std::function<>).
- Lambdas can have default parameter values, like any other function.
auto lambda = [](int a, int b=1) -> int {
return a + b;
};- Lambdas can be generic, accepting any types for parameters.
auto lambda = [](auto a, auto b=1) {
return a + b;
};- Introduces init-captures, allowing initialization of captured values.
auto lambda = [sum = 0](auto value) mutable {
sum += value;
return sum;
};Note: I find it especially useful to capture std::shared_from_this() / std::enable_shared_from_this<>.
Since the lambda type must be auto, in C++11 we cannot return a lambda from a function. This is fixed in C++14 as functions are allowed to return auto following the deduction guidelines.
auto make_adder(int x) {
return [x](int y) { return x + y; };
}
auto adder = make_adder(5);
int result = adder(3); // result == 8- Before C++17, capturing
thismeant capturing the pointer only. C++17 allows capturing a copy of*this.
class Cls {
public:
Cls(int v) : value(v) {}
auto getLambda() const {
return [*this]() { return value * value; }; // captures a copy of *this
}
protected:
int value;
};
std::function<int()> func;
{
Cls cls { 10 };
func = cls.getLambda();
}
printf("%d\n", func()); // 100- Lambdas can be
constexprif their bodies are constexpr-evaluable.
constexpr auto add = [](auto a, auto b) {
return a + b;
};
static_assert(add(2, 1) == 3);- Allows specifying template parameters explicitly for lambdas.
//-------------------------------------
// Example: Variadic sum using lambda
//-------------------------------------
template<typename... Args>
int add(Args &&...args) {
auto lambda = [...args = std::forward<Args>(args)]() {
return (args + ...); // fold expression (C++17)
};
return lambda();
}
//-------------------------------------
// Example: Compile-time optimized power
//-------------------------------------
template <typename T>
auto Pow = [](T base, T exponent) {
if constexpr (std::is_integral_v<T>) {
return ipow(base, exponent); // Optimized for integer types
}
else if constexpr (std::is_same_v<T, float>) {
return std::powf(base, exponent);
}
else {
// fallback to std::pow
return std::pow(base, exponent); // Default (maybe imaginary numbers?)
}
};- An rvalue reference (
&&) can bind to temporary objects (rvalues). - Enables move semantics: transferring resources from temporaries to named objects efficiently.
int a = 3;
int b = 4;
int &&rvalue = a + b; // binds to rvalue (3 + 4 == 7)static_assertchecks a compile-time boolean expression; if false, produces a compilation error.- Pre-C++11 options (
assert,#error) were either too late or too early in compilation.
static_assert(sizeof(void *) == sizeof(uint32_t), "We store pointers in uint32_t fields")
template<typename Integral>
Integral foo(Integral x) {
static_assert(std::is_integral<Integral>::value, "foo() parameter must be an integral type");
return x;
}- Allows
sizeofto work on non-static data members without needing an object instance.
struct A {
int data;
double more;
};
auto offset = offsetof(A, data); // standard C macro
auto size_of_member = sizeof(A::more); // allowed in C++11 and laterC++11 allows variable alignment to be queried alignof and queried alignas.
-
alignof(T): Takes the type and returns the alignment requirement (in bytes) of type ``T.- For references, it returns the referenced type's alignment.
- For arrays, it returns the element type's alignment.
-
alignas(): Specifies the memory alignment for a variable or type.alignas(T)is shorthand foralignas(alignof(T)).
struct alignas(16) Vec4 {
float x, y, z, w;
};
static_assert(alignof(Vec4) == 16);
alignas(float) unsigned char matrix4x4[sizeof(float) * 16]Allows each thread to have its own separate instance of a variable.
It also works for static variables defined inside a function. Each thread will have its own instance of the per thread global variable.
- Lifetime: The lifetime of a TLS variable begins when it is initialized and ends when the thread terminates.
- Visibility: TLS variables have visibility at the thread level.
- Scope: TLS variables have scope depending on where they are declared.
void log(const char *name) {
thread_local int counter{};
printf("%s: %d\n", name, counter++);
}
thread_local int inc {};
void
doCount(const char *name, int count, int &ref) {
for (int i=0; i<count; ++i) {
++inc; // Increases its local copy
log(name);
}
ref = inc; // inc is local copy
}
int
main() {
int a{}, b{};
std::thread ta([&a] { doCount("ta", 10, a); }); // std::thread was introduced in C++11
std::thread tb([&b] { doCount("tb", 20, b); });
tb.join();
ta.join();
printf("a=%d, b=%d, inc=%d\n", a, b, inc); // a=10, b=20, inc=0
return 0;
}Provide a unified standard syntax for implementation-defined language extensions. Before this feature, each compiler has its own way to do it:
- GNU/Clang: attribute((...))
- Microsoft: __declspec(...)
- Borland: __property
- Different compilers: __builtin_XXX
The new standard way is: [[attribute, attribute...]].
These are the standard attributes:
- [[noreturn]] [C++11] Indicates that the function does not return.
- [[carries_dependency]] [C++11] Indicates that dependency chain in release-consume std::memory_order propagates in and out of the function.
- [[deprecated]] [C++14] Indicates that the use of the name or entity declared with this attribute is allowed, but discouraged for some reason.
[[deprecated("reason")]] [C++14]
- [[fallthrough]] [C++17] Indicates that the fall through from the previous case label (switch statement) is intentional and should not be diagnosed by a compiler that warns on fall-through.
- [[nodiscard]] [C++17] Encourages the compiler to issue a warning if the return value is discarded.
[[nodiscard("reason")]] [C++20]
- [[maybe_unused]] [C++17] Suppresses compiler warnings on unused entities, if any.
- [[likely]] [C++20]
- [[unlikely]] [C++20] Indicates that the compiler should optimize for the case where a path of execution through a statement is more or less likely than any other path of execution.
- [[no_unique_address]] [C++20] Indicates that a non-static data member need not have an address distinct from all other non-static data members of its class.
- [[assume(expression)]] [C++23] Specifies that the expression will always evaluate to true at a given point.
Each compiler/library can create its own attributes inside a namespace:
- Microsoft: [[msvc::attribute]]
- Guidelines Support Library: [[gls::attribute]]
- GNU: [[gnu::attribute]]
- Clang: [[clang::attribute]]
Since C++17, we can use [using namespace: atribute, attribute, ...].
- Allows providing default member initializers inside the class definition.
struct A {
int value = 0;
};static inlinevariables can be initialized inside the class definition, eliminating the need for an out-of-class definition.
struct A {
static inline int value = 0;
};
// Pre-C++17
// Header
struct A {
static int value;
};
// Code
int A::value = 0;- A constructor can delegate to another constructor in the same class using the member initializer list.
struct A {
A() : A(-1) {} // delegates to A(int)
A(int v) : value(v) {}
int value;
};- A derived class can inherit constructors from its base class using
using Base::Base;.
struct A {
A() : A(-1) {}
A(int v) : value(v) {}
int value = 0;
};
struct B : public A {
using A::A; // inherit A's constructors
};
B b1; // calls A()
B b2(123); // calls A(int)- A move constructor is a special type of constructor that allows the efficient transfer of resources (like memory ownership) from a temporary object to another object.
- It's invoked automatically when you initialize an object with an rvalue.
- Move constructors are typically used to avoid unnecessary copies of objects, improving performance.
class Cls {
public:
Cls() { ptr = new uint8_t; *ptr = 0; } // Creates and initializes a pointer
Cls(const Cls &other) { ptr = new uint8_t; *ptr = *other.ptr; } // Creates a pointer and copies the values of other object's pointer
Cls(Cls &&other) { std::swap(ptr, other.ptr); } // Steals the other object's pointer because it's temporary and will be destroyed
~Cls() { if (ptr != nullptr) delete ptr; } // Destroys the pointer
protected:
uint8_t *ptr = nullptr;
};- Declaring a conversion operator as
explicitprevents implicit conversions.
struct Bool {
explicit Bool(bool v) : value(v) {}
explicit operator bool() const { return value; }
explicit operator std::string() const {
return value ? "true" : "false";
}
bool value {};
};
Bool b0(true); // Ok
Bool b1 { true }; // Ok
Bool b2 = { true }; // Error: constructor is explicit
Bool b3 = true; // Error: constructor is explicit
bool flag1 = static_cast<bool>(b1); // Ook
bool flag2 = b1; // Error: operator bool is explicit
if (b1) {
printf("Ok!\n");
}
// Error: operator bool is explicit. No automatic conversion to bool, that is 0 or 1, and then automatic conversion to int.
if (b1 < 123) {
printf("Noooo!\n");
}
std::string str = b1; // Error: operator std::string() is explicit
std::string str = static_cast<std::string>(b1); // explicit cast is alowedoverridekeyword ensures that a virtual function truly overrides a base-class function; otherwise the compiler issues an error.
struct A {
virtual void foo() { printf("A::foo\n"); }
virtual void bar() { printf("A::bar\n"); }
};
struct B : public A {
void foo(int) { printf("B::foo(int)\n"); } // Does not override A::foo (maybe an error)
void foo() const { printf("B::foo() const\n"); } // Does not override A::foo (maybe an error)
void bar() const override { printf("B::bar() const\n"); } // Error: signature mismatch (original bar is not const)
void bar() override { printf("B::bar()\n"); } // Ok
};
A *a = new B;
a->foo(); // A::foo
a->bar(); // B::bar- A class declared as
finalcannot be inherited from. - A virtual function marked
finalcannot be overridden in further derived classes.
struct A final {
void foo() {}
};
// struct B : public A {}; // Error: cannot inherit from final class
struct Base {
virtual void foo() {}
};
struct B : public Base {
void foo() final {} // Cannot be overridden further
};
struct C : public B {
void foo() override {} // Error: B::foo is final
};The return value could be one of:
std::strong_ordering: f(a) must be equal to f(b). Only one of (a < b), (a == b) or (a > b) must be true.std::weak_ordering: f(a) may be different from f(b). Only one of (a < b), (a == b) or (a > b) must be true.std::partial_ordering: f(a) may be different from f(b). (a < b), (a == b) and (a > b) may all be false.intor other type.
#include <compare>
struct Point {
float x, y;
constexpr auto operator <=>(const Point &rhs) const {
if(x < rhs.x) return -1;
if(x > rhs.x) return 1;
if(y < rhs.y) return -1;
if(y > rhs.y) return 1;
return 0;
};
// Alternative:
constexpr auto operator<=>(const Point &rhs) const {
if (x < rhs.x) return std::weak_ordering::less;
if (x > rhs.x) return std::weak_ordering::greater;
if (y < rhs.y) return std::weak_ordering::less;
if (y > rhs.y) return std::weak_ordering::greater;
return std::weak_ordering::equivalent;
}
};- Alternatively, you can write:
struct Point {
float x, y;
auto operator<=>(const Point&) const = default; // compiler-generated
};Note: The default version ususallly generates better and faster code.
= defaulttells the compiler to generate the default implementation of constructors, destructors, or assignment operators.
struct A {
A() = default;
A(const A &) = default;
A(A &&) = default;
~A() = default;
A & operator = (const A &) = default;
A & operator = (A &&) = default;
auto operator <=>(const A &rhs) const = default; // C++20
bool operator == (const A &rhs) const = default; // C++20
bool operator != (const A &rhs) const = default; // C++20
bool operator < (const A &rhs) const = default; // C++20: default is delete
bool operator <= (const A &rhs) const = default; // C++20: default is delete
bool operator > (const A &rhs) const = default; // C++20: default is delete
bool operator >= (const A &rhs) const = default; // C++20: default is delete
};= deleteprevents the compiler from generating or using the default version of a function.
struct A {
A() = delete;
A(const A &) = delete;
A(A &&) = delete;
~A() = delete;
A & operator = (const A &) = delete;
A & operator = (A &&) = delete;
// Won't need anymore this old idiom
A & operator = (const A &); // Not implemented anywere (get a compiler error if someone tries to use it)
};- Allows overloading member functions based on whether the object is an lvalue or rvalue.
- Syntax:
void foo() &(lvalue only) orvoid foo() &&(rvalue only).
struct Cls {
void foo() & { printf("lvalue\n"); }
void foo() && { printf("rvalue\n"); }
void foo() const & { printf("const lvalue\n"); }
void foo() const && { printf("const rvalue\n"); }
};
const Cls getCls() { return Cls(); }
int main() {
Cls cls;
const Cls cCls;
cls.foo(); // lvalue
cCls.foo(); // const lvalue
Cls().foo(); // rvalue
getCls().foo(); // const rvalue
return 0;
}- Introduces a clearer syntax for ref-qualifiers by explicitly naming the
thisparameter.
struct Cls {
void foo() & { printf("foo lvalue\n"); } // C++11
void foo() && { printf("foo rvalue\n"); } // C++11
void foo() const & { printf("foo const lvalue\n"); } // C++11
void foo() const && { printf("foo const rvalue\n"); } // C++11
void bar(this Cls &self) { printf("bar lvalue\n"); } // C++23
void bar(this Cls &&self) { printf("bar rvalue\n"); } // C++23
void bar(this const Cls &self) { printf("bar const lvalue\n"); } // C++23
void bar(this const Cls &&self) { printf("bar const rvalue\n"); } // C++23
};
const Cls getCls() { return Cls(); }
int main() {
Cls cls;
const Cls cCls;
cls.foo(); // lvalue
cCls.foo(); // const lvalue
Cls().foo(); // rvalue
getCls().foo(); // const rvalue
//--
cls.bar(); // lvalue
cCls.bar(); // rvalue
Cls().bar(); // rvalue
getCls().bar(); // const rvalue
return 0;
}Since C++23 we can 'deduce this' using a template to simplify the code:
struct Cls {
// A lot of boilerplate
std::string & getName(this Cls &self) { return mName; } // C++23
const std::string & getName(this const Cls &self) { return mName; } // C++23
std::string && getName(this Cls &&self) { return std::move(mName); } // C++23
// Better using 'deducing this'
template <typename Self>
auto && getName(this Self &&self) { return std::forward(mName); } // C++23
std::string mName;
};Another nice use of 'deduce this' is making recursive lambdas:
// C++14 version
auto fibonacci_14 = [](const auto &self, int n) -> int {
return n < 2 ? n : self(self, n-1) + self(self, n-2);
};
// But it is a little bit ugly
auto a = fibonacci_14(fibonacci_14, 5);
// Alternative C++14
int foo() {
// This only works as local lambda.
// We cannot use auto here.
static std::function<int(int)> fibonacci_14_alt = [&](int n) -> int {
return (n < 2) ? n : fibonacci_14_alt(n - 1) + fibonacci_14_alt(n - 2);
};
return fibonacci_14(fibonacci_14, 5);
}
//--
// C++23 version using deduce this
auto fibonacci_23 = [](this auto &self, int n) -> long {
return (n < 2) ? n : self(n-1) + self(n-2);
};
auto a = fibonacci_23(5);enum class(orenum struct) defines an enumeration with a scoped name and optionally a fixed underlying type.- No implicit conversions to integer, improving type safety.
// Note: The type is optional
enum class Status : uint8_t {
ON = 0,
OFF,
};
Status s1 = Status::ON; // Ok
Status s2 = ON; // Error: must use Status::ON
Status s3 = 0; // Error: no implicit conversion
int x = static_cast<int>(s1); // Ok
int y = s1; // Error: no implicit conversion- Allows a single
operator[]to accept multiple indices. - Syntax:
T& operator[](size_t x, size_t y, size_t z);
// Instead of having to use data[x][y][z]
// Now we can have multiple indexes
T & operator[](size_t x, size_t y, size_t z) { }
const T & operator[](size_t x, size_t y, size_t z) const { }
// and we can use it that way:
data[x, y, z] = 123;
auto value = data[x, y, z];- Tells the compiler not to instantiate a template in this translation unit; instantiation must occur elsewhere.
- Reduces compile times when templates are used across multiple translation units.
// In header file:
extern template class std::vector<MyClass>;
// In one .cpp file:
template class std::vector<MyClass>; // Actual instantiationFinally! Allows nested template instantiations without spaces between consecutive > characters.
// Before C++11:
std::vector<std::vector<std::vector<std::vector<std::vector<int> > > > > vec;
// Since C++11:
std::vector<std::vector<std::vector<std::vector<std::vector<int>>>>> vec;- Deduce template arguments from constructor arguments when instantiating objects.
Example:
std::vector vec = {1, 2, 3, 4}; // CTAD: deduces std::vector<int>usingsyntax provides alias templates, replacing verbosetypedefdeclarations.
// Before C++11:
typedef std::vector<int> vecInt;
// Since C++11:
using vecInt = std::vector<int>;
//-------------------------------------
template <typename T, typename U>
struct Pair { ... };
// Before C++11, cannot partially apply template parameters:
typedef Pair<int, double> PairIntDouble;
// With alias templates:
template <typename U>
using IntPair = Pair<int, U>;- Enables functions or classes to accept an arbitrary number of template parameters or function arguments.
template<typename... Values> class tuple; // takes zero or more arguments
template<typename Head, typename... Tail> class tuple; // takes one or more arguments
//-------------------------------------
// Example: Variadic function 'print'
//-------------------------------------
template<typename T>
void print(const T& value) {
std::cout << value << "\n";
}
template<typename T, typename... Args>
void print(const T& first, const Args&... rest) {
std::cout << first << "\n";
print(rest...); // Recursive call to print the rest of the arguments
}
//-------------------------------------
// Example: Variadic function 'sum'
//-------------------------------------
template<typename Head>
Head sum(Head value) {
return value;
}
template<typename Head, typename ...Tail>
Head sum(Head head, Tail ...tail) {
return head + sum(tail...);
}
auto res = sum(1, 2.61f, 3.42, true); // int res = 8;- Simplifies variadic template code by collapsing parameter packs with a binary operator.
Example:
template<typename... Args>
auto sumAll(Args... args) {
return (args + ...); // equivalent to a + b + c + ...
}
template<typename T, typename... Rest>
void print_all(const T& first, const Rest&... rest) {
std::cout << first;
((std::cout << ", " << rest), ...);
std::cout << "\n";
}- String literal constants [C++11]
char *str = "Hello"; // Deprecated: mutable pointer to string literal
const char *str = "Hello"; // Recommended: pointer to const char- Unexpected handler [C++11]
std::unexpected_handler, std::set_unexpected(), std::get_unexpected() and other related features are deprecated
-
std::auto_ptr [C++11] (removed in C++17)
// Deprecated in C++11 std::auto_ptr<int> p(new int(5)); // Prefer std::unique_ptr or std::shared_ptr std::unique_ptr<int> p2 = std::make_unique<int>(5);
-
register keyword [C++11]
// register int x = 0; // Deprecated. Compiler decides storage automatically. -
++ bool operator [C++11]
// bool b = true; // ++b; // Deprecated: increments boolean (conceptually toggles).
-
C-style casts [C++11]
Use any of static_cast<>(), reinterpret_cast<>() or const_cast<>()
// int x = (int)3.14; // Old style: C-style cast
int y = static_cast<int>(3.14); // Prefer C++-style cast-
Some C standard headers (deprecated or removed)
Empty C headers
<ccomplex>/<complex.h>[C++11: deprecated in C++17; removed in C++20]
Include the header<complex><ctgmath>/<tgmath.h>[C++11: deprecated in C++17; removed in C++20]
Include the headers<complex>and<cmath>the overloads equivalent to the contents of the C header tgmath.h are already provided by those headers
Meaningless C headers
<ciso646>[Removed in C++20]
Empty header. The macros that appear in iso646.h in C are keywords in C++<cstdalign>[C++11: deprecated in C++17; removed in C++20]
Defines one compatibility macro constant<cstdbool>[C++11: deprecated in C++17; removed in C++20]
Defines one compatibility macro constant<iso646.h>
Has no effect<stdalign.h>[C++11]
Defines one compatibility macro constant<stdbool.h>[C++11]
Defines one compatibility macro constant