- preprocessor directives
- Constants
- Literals
- Initialization
- Type inference
- Control flow enhancements
- Lambdas
- Other interesting parts
- Object Oriented
- Initializing class member variables [C++11]
- Initializing static class member variables [C++17]
- Delegate constructor [C++11]
- Inherit constructors [C++11]
- Move constructors [C++11]
- Explicit conversion operators [C++11]
- Explicit virtual function override [C++11]
- final [C++11]
- Operator spaceship <=> (3 way comparator) [C++20]
- Generate default function [C++11]
- Explicit delete default function [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, I have decided to add most of the directives in this section.
#if expression
#ifdef id // #if defined(id)
#ifndef id // #if !defined(id)
#elif expression
#else
#endif
// New
#elifdef id // #elif defined(id) [C++23]
#elifndef id // #elif !defined(id) [C++23]
#define id [expressions]
#define id(params) [expressions]
#define id(params, ...) [expressions] // [C++11] We can use __VA_OPT__() and __VA_ARGS__ to manage the ellipsis
#define id(...) [expressions] // [C++11] We can use __VA_OPT__() and __VA_ARGS__ to manage the ellipsis
#undef id
// __VA_ARGS__ is not always available before C++11.
// __VA_OPT__ is available since 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 Concatenation ##: Concatenates two strings.
#define kStringify(x) #x
#define kConcat(x, y) x##y
int result = kConcat(num, 1) + kConcat(num, 2);
printf("%s = %d\n", kStringify(result), result);
#include <header> // global
#include "header" // local
#__has_include(<header>) // global [C++17]
#__has_include("header") // local [C++17]
#if defined(__has_include)
#if __has_include (<stdatomic.h>)
#include <stdatomic.h>
#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(value) // Sets the current alignment to value
#pragma pack() // Sets the current alignment to the default value
#pragma pack(push) // Stores the current alignment value
#pragma pack(push, value) // Stores the current alignment and sets it to 'value'
#pragma pack(pop) // Restores the last alignment value
Sadly, not all compilers define them. So we cannot trust them.
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. Not standard
__FUNCDNAME__ Current function name. Not standard
__PRETTY_FUNCTION__ Current function name. Not standard [GCC]
__FUNCSIG__ Current function name. Not 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 nullptr is nullptr_t.
- It replaces the macro
NULL
, adding semantic meaning. nullptr
is not0, 0x0, ((void *) 0), ...
.
Benefits:
- Improves code readability and maintainability.
Example:
void foo(char *);
void foo(int);
foo(NULL); // Which function is called?
foo(nullptr); // foo(char *) is called
-
constexpr
variables are evaluated at compile time and cannot be modified.- Native types:
constexpr
impliesconst
(bool, int, float, uint64_t, ...). - Pointers:
constexpr
implies* const
. NOTconst *
.
- Native types:
-
constexpr
functions can be executed at compile or at execution time. -
C++11: Limited to a single
return
expression and cannot contain control structures. -
C++14: Allows the use of conditionals (
if
,switch
), loops, and multiple statements.-
Class types can have mutable members within
constexpr
functions.// Since C++14 class Accumulator { public: constexpr Accumulator() : sum(0) {} constexpr void add(int value) { sum += value; } constexpr int total() const { return sum; } private: int sum; }; constexpr int computeSum() { Accumulator acc; for (int i = 1; i <= 5; ++i) { acc.add(i); } return acc.total(); }
-
-
C++17: Introduces
constexpr
lambdas. -
C++20: Allows
constexpr
destructors and virtual functions, and the use of exceptions inconstexpr
contexts.
// Special case. 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;
}
Benefits:
- Improves performance by pre-computing values.
- Enables compile-time checks and optimizations.
- Facilitates metaprogramming techniques.
Example:
constexpr float pi = 3.14f; // constexpr value
constexpr double earthGravity = 9.8;
constexpr double moonGravitay = earthGravity / 6.0; // We cannot do this with const in c++03
// C++11 allows only non-complex computations
constexpr int add(int a, int b) {
return a + b;
}
// Can be also be used in classes
struct S {
constexpr void foo(int) {}; // Can be executed at compile or execution time
void foo(int) {}; // Error: Collides with prior function
void foo(int) const {}; // Ok: constexpr does not imply const
};
// C++14 adds common syntax such as if statements, multiple returns, loops, etc.
constexpr int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int v = 5;
constexpr int resultA = factorial(add(2, 3)); // Solved at compile time
constexpr int resultB = add(v, v); // Error (because v)
const int resultC = add(v, v); // Solved at execution time (because v)
int resultD = add(1, 1); // Solved at compile time
int arrayA[resultA]; // OK
int arrayB[resultB]; // Error
int arrayC[resultC]; // Error
// C++17 allows constexpr lambda functions (see #lambdas)
auto identity = [](int n) constexpr { return n; };
// C++20 allows constexpr virtual functions
struct A {
virtual int foo() const = 0;
};
struct B : public A {
constexpr virtual int foo() const { return 42; }
};
constinit
guarantees that a variable with static storage duration is initialized at compile time.- The variable is still mutable.
- It can be used in global objects or objects declared with static or extern.
- Solves the static initialization order fiasco.
Benefits:
- Improves code safety by avoiding undefined behavior.
- Facilitates static analysis and optimizations.
Example:
constexpr int valueCE = 3;
constinit int valueCI = valueCE;
constinit int error = valueCI; // Error: valueCI is not usable in constant expressions
void foo() {
valueCE++; // Error: valueCE is constexpr
valueCI++; // Ok
}
- Generates an inmediate function.
- Cannot be executed at runtime.
Benefits:
- Improves performance by pre-computing values.
- Enables compile-time checks and optimizations.
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
Detects whether the function call occurs within a constant-evaluated context.
Example:
constexpr double
power(double b, int x)
{
if (std::is_constant_evaluated()) {
//
}
else {
return std::pow(b, double(x));
}
}
- 0b or 0B followed by one or more binary digits (0, 1).
Example:
int a = 0b0001;
unsigned int b = 0B0010;
- The single-quote character ' may be used arbitrarily as a digit separator in numeric literals.
- 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'78f;
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 Hexadedimal exponent 0x2.1p0 = (2 + 1/16) * 2^0 = 2.0625
Hexadecimal float regex:
[+-]? 0 [xX] ( [0-9a-f]* . [0-9a-f]+ | [0-9a-f]+ .? ) [pP] [+-]? [0-9]+ [flL]?
0x2p1f = 2 * 2^1 = 4.0f
-0x2.1p0 = (2 + 1/16) * 2^0 = -2.0625
0x1.0p10L = 1024.0L
0x0.8p-1 = 0.25
L 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: Does not work pretty well on some compilers.
Example:
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
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"
- Delimiter can be any character except the single quote and double quote.
- Allows for flexibility in escaping nested quotes within the raw string.
Example:
const char *raw = R"(
<html>
<body>
</body>
</html>
)";
Defined in inline namespace std::literals::complex_literals
[C++14]
A std::complex
literal representing pure imaginary number
Example:
if std::complex<float> 5if
i std::complex<double> 5i
il std::complex<long double> 5il
Defined in inline namespace std::literals::chrono_literals
[C++14]
h A std::chrono::duration literal representing hours
min A std::chrono::duration literal representing minutes
s A std::chrono::duration literal representing seconds
ms A std::chrono::duration literal representing milliseconds
us A std::chrono::duration literal representing microseconds
ns A std::chrono::duration literal representing nanoseconds
y A std::chrono::year literal representing a particular year [C++20]
d A std::chrono::day literal representing a day of a month [C++20]
Example:
using namespace std::literals::chrono_literals;
auto timeout = 5min + 30s;
Allows the user to define his own suffixes and define conversion rules.
Rules:
- All the user defined literals must begin always with the underscore '_'.
- The system libraries must not use underscores to define its own literals.
- Only the following parameter lists are allowed on literal operators:
// Numeric
( unsigned long long int )
( long double )
// Characters
( char )
( wchar_t )
( char8_t ) // C++20
( char16_t )
( char32_t )
// Strings
( const char * )
( const char * , std::size_t )
( const wchar_t * , std::size_t )
( const char8_t * , std::size_t ) // C++20
( const char16_t * , std::size_t )
( const char32_t * , std::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);
}
sprite.Rotate(180_deg);
std::initializer_list<T>
is a standard library container that represents a sequence of elements. It's primarily used for initializing standard library containers.- It allows you to initialize these containers with a list of elements in a convenient and concise way.
- Cannot be directly modified or accessed by index.
Benefits:
- Improves code readability and conciseness.
- Can be more efficient than manual element addition.
- Enables features like uniform initialization.
- Copying an initializer_list doesn't copy the actual elements, only references them.
Example:
#include <initializer_list>
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 and concise syntax for initializing objects in various contexts.
- It allows you to initialize objects using braces {} regardless of the type of object being initialized, whether it's a fundamental type, user-defined type, or an aggregate type.
- Uniform initialization prevents narrowing conversions. The type in braces must be the same of the variable.
Example:
int x { 42 };
double y { 3.14 };
int z { 3.14 }; // Error: narrowing conversion
int w { }; // 0
int *p { }; // nullptr
int arr[] { 1, 2, 3, 4 };
struct Struct {
std::string name;
int data;
};
Struct obj1 { "Carlos", 10 };
std::vector<float> vec {1, 2, 3};
- 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 = {}
Point p3 = { .z = 30, .x = 10 }; // Error: Order is important
Type inference helps reduce boilerplate code by letting the compiler automatically deduce the type of a variable or function return value from its context.
- Replaces explicit type declarations, especially for long or complex types.
- Very useful with long type names.
- Improves code readability and reduces verbosity.
auto itr = std::find(begin(vec), end(vec), 4);
// instead of
std::vector<int>::iterator itr = std::find(begin(vec), end(vec), 4);
// C++14 allows also deduce return type but no function arguments
template<typename T, typename U>
auto add(A a, B b) {
return x + y;
}
// C++20 allows use auto as function arguments
auto
add(auto a, auto b) {
return (a + b);
}
auto a = 2.3f;
auto b = 1;
decltype(a+b) c;
if (std::is_same<decltype(c), int>::value)
std::cout << "type c == int" << std::endl;
if (std::is_same<decltype(c), float>::value)
std::cout << "type c == float" << std::endl
Deduces types keeping their references and cv-qualifiers, while auto will not.
int y = 0;
const int & y2 = y;
const auto y3 = y2; // const int
decltype(auto) y4 = y2; // const int &
- In template functions, specifies the return type based on the operations performed.
- Enables template functions to return types based on their arguments and operations.
template<typename A, typename B>
auto add(A a, B b) -> decltype(a + b) {
return a + b;
}
// std::vector vec;
for(auto &value : vec) {
value += 3;
}
for(size_t index = 0; const auto &value : vec) {
printf("- %d: %s\n", index++, value.c_str());
}
Reduces variable scope.
if(auto it = std::find(begin(vec), end(vec), 4); it != end(vec)) {
*it = -4;
}
- Evaluates at compile time if all conditions and branches are constexpr.
- 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 if we are at compile time.
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 base;
uint64_t res{1};
while (exp) {
if (exp & 1) res *= base;
exp /= 2;
base *= base;
}
return res;
}
constexpr uint64_t ipow(uint64_t base, uint8_t exp) {
if consteval { // use a compile-time friendly algorithm
return ipow_ct(base, exp);
}
// use runtime evaluation
return std::pow(base, exp);
}
Note: The alternative for C++20 if using the function std::is_constant_evaluated()
Reduces variable scope.
switch(auto status = GetStatus(); status) {
case Init:
break;
case Run:
break;
}
Lambda expressions provide a concise way to create anonymous functions.
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 & _ex) : ex{_ex} { }
inline int operator()(int a, int b) const { return ex + a + b; }
private:
int ex;
};
- captureList: is optional.
- [&]: capture all by reference.
- [=]: capture all by value (default).
- [&foo, bar]: Capture
foo
by reference andbar
by value.
- parameters: is optional.
- mutable: is optional.
By default, the generated operator() is const, so if we want to be able to modify the captured variables we need to addmutable
after parameters.
auto lambda = [foo](int a, int b) mutable { return ++foo + a + b; }; // Increment local lambda variable foo on every call
- returnType: is optional and can be deduced.
- body: is the body of the function to execute.
Note: Lambda type must be auto.
In C++14, lambdas can take default values for parameters, like any other function:
auto lambda = [](int a, int b=1) -> int { return a + b; };
In C++14, lambda parameters can accept generic types:
auto lambda = [](auto a, auto b=1) -> int { return a + b; };
In C++14, we can initialise captured values:
auto lambda = [foo=0](auto a, auto b=1) -> int { return ++foo + a + b; };
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.
Prior to C++17, capturing this
by value in a lambda, inside a member function, was not allowed. C++17 relaxed this restriction, allowing lambdas to capture a copy of this.
class Cls {
public:
Cls(int v) : value(v) {}
auto getLambda() const { return [this]() { return value * value; }; }
protected:
int value;
};
std::function<int()> func;
{
Cls cls { 10 };
func = cls.getLambda();
}
printf("%d\n", func()); // 100
C++17 extended the capabilities of constexpr
functions to include lambda expressions, allowing them to be evaluated at compile-time if their arguments are constexpr and their bodies are constexpr-evaluable.
constexpr auto lambda = [](auto a, auto b) { return a + b; };
static_assert(lambda(2, 1) == 3);
This makes it easier to access the template parameter type.
//-------------------------------------
template<typename... Args>
int add(Args &&...args) {
auto lambda = [...args = std::forward<Args>(args)]() {
return (args + ...);
};
return lambda();
}
//-------------------------------------
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_floating_point_v<T>) {
return fpow(base, exponent); // Optimized for float and double
}
else {
return std::pow(base, exponent); // Default (maybe imaginary numbers?)
}
};
- An rvalue reference is a special type of reference that can bind to temporary objects.
- Rvalue references are denoted by the double ampersand (&&).
- They allow you to identify and distinguish temporary objects from regular ones.
int a = 3;
int b = 4;
int &&rvalue = a + b;
Before C++11, there were two ways to check assertions, macro assert in the header <assert.h>
/ <cassert>
, and the preprocessor word #error
but they didn't work well with templates.
The checks happened either too early (before templates were set up) or too late (after the program was running).
C++11 introduces the keyword static_assert
to solve this issues.
static_assert(sizeof(void *) == sizeof(uint32_t), "We store pointers in uint32_t fields")
template<class Integral>
Integral foo(Integral x) {
static_assert(std::is_integral<Integral>::value, "foo() parameter must be an integral type.");
}
struct A {
...
Class cls;
...
};
ptr += sizeof(A::cls);
C++11 allows variable alignment to be queried alignof
and controlled alignas
.
The alignof
operator takes the type and returns the power of 2 byte boundary on which the type instances must be allocated.
For references, it returns the referenced type's alignment.
For arrays, it returns the element type's alignment.
The alignas
specifier controls the memory alignment for a variable.
alignas(T)
is shorthand for alignas(alignof(T))
.
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 count{};
printf("%s: %d\n", name, count++);
}
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 standar 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, ...]
.
struct A {
int value = 0;
};
struct A {
static inline int value = 0;
};
// prior to C++17
struct A {
static int value;
};
int A::value = 0;
struct A {
A() : A(-1) {}
A(int v) : value(v) {}
int value;
};
struct A {
A() : A(-1) {}
A(int v) : value(v) {}
int value = 0;
};
struct B : public A {
using A::A;
};
B b1;
B b2(123);
- 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;
};
When you declare a conversion operator with the explicit keyword, it prevents the compiler from performing implicit conversions using that operator.
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 b1 { true }; // Ok
Bool b2 = { true }; // Error: constructor is explicit
Bool b3 = true; // Error: constructor 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 alowed
Reduces errors when inherit from other classes.
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\n"); } // Does not override A::foo (maybe an error)
void foo() const { printf("B::foo\n"); } // Does not override A::foo (maybe an error)
void bar() const override { printf("B::bar\n"); } // Error (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
struct A final {
};
// Error cannot inherit from A
struct B : public A {
};
//--
struct A {
virtual void foo();
};
struct B : public A {
virtual void foo() final;
};
struct C : public B {
virtual void foo(); // 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 falseint, ...
#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;
};
};
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
};
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)
};
With this feature we can indicate when we want to use a class function for l-values or r-values.
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(); // rvalue
Cls().foo(); // rvalue
getCls().foo(); // const rvalue
return 0;
}
C++23 introduces clearer syntax for Ref qualifiers.
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(); // rvalue
Cls().foo(); // rvalue
getCls().foo(); // const rvalue
//--
cls.bar(); // lvalue
cCls.bar(); // rvalue
Cls().bar(); // rvalue
getCls().bar(); // const rvalue
return 0;
}
And 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 fibonacci14 = [](const auto &fibonacci, int n) -> long {
return n < 2 ? n : fibonacci(fibonacci, n-1) + fibonacci(fibonacci, n-2);
};
// But it is a little bit ugly
auto a = fibonacci14(fibonacci14, 5);
//--
// C++23 version using deduce this
auto fibonacci23 = [](this auto &fibonacci, int n) -> long {
return (n < 2) ? n : fibonacci(n-1) + fibonacci(n-2);
};
auto a = fibonacci23(5);
// Note: The type is optional
enum class Status : uint8_t {
ON = 0,
OFF,
};
Status s1 = ON; // Error
Status s2 = Status::ON; // Error
Status s3 = 0: // Error
// 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 use it that way:
data[x, y, z] = 123;
auto value = data[x, y, z];
- Allows the compiler not to instantiate the template in this translation unit. Will be instantiated in another unit.
- Reduces compile time.
extern template class std::vector<Cls>;
Finally! We can close several templates at once without separating the '>' symbol.
std::vector<std::vector<std::vector<std::vector<std::vector<int>>>>> vec; // This is finally valid instead of
// std::vector<std::vector<std::vector<std::vector<std::vector<int> > > > >
Before C++11 we could not typedef aliases. Now we can with the reserved word using
that simplifies typedefs.
// Using syntax is simpler than typedef syntax
typedef std::vector<int> vecInt;
using vecInt = std::vector<int>;
//-------------------------------------
template <typename A, typename B>
struct Template { ... };
// We could not do this in C++03
template <typename B>
using TemplateInt = Template<int, B>;
Allows you to create functions or classes that can take an arbitrary number of arguments of different types.
template<typename... Values> class tuple; // takes zero or more arguments
template<typename Head, typename... Tail> class tuple; // takes one or more arguments
//-------------------------------------
// Example: print
//-------------------------------------
template<typename T>
void print(T value) {
std::cout << value << std::endl;
}
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << std::endl;
print(rest...); // Recursive call to print the rest of the arguments
}
//-------------------------------------
// Example: 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;
- String literal constants [C++11]
char *str = "Hello"; // Deprecated
const char *str = "Hello"; // Ok
-
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)
Preferstd::unique_ptr
-
register keyword [C++11]
-
++ bool operator
-
C casting
Use any ofstatic_cast
,reinterpret_cast
orconst_cast
-
Some C standar libraries
Empty C headers
<ccomplex> / complex.h>
(C++11) (deprecated in C++17) (removed in C++20)
Simply includes the header<complex>
<ctgmath> / <tgmath.h>
(C++11) (deprecated in C++17) (removed in C++20)
Simply includes 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