Version_Summary - RicoJia/notes GitHub Wiki
========================================================================
- std::map
std::map<std::string, int> map_ = {{"foo", 191}, {"bar", 192}};
========================================================================
========================================================================
- auto's constraints:
- cannot be used for params
cpp int add(auto x); // error: auto not allowed in function prototype
- cannot be used with arrays
cpp auto arr[2] = {arr};
- decltype: very similar to
typeof
:
- used to deduct if type of an expression
auto x = 1; decltype(x) z;
- good for type comparison
if (std::is_same<decltype(x), int>)
- constraints: you can't use it directly as a return type
decltype(x+y) add(T x, U y); //x and y are undefined when deducting decltype(x+y)
-
trailing return type with auto makes use of decltype
template<typename T, typename U> auto add(T x, U y) -> decltype(x+y){ return x + y. }
- but in c++ 14, you can use
auto
directlyauto add3(T x, U y);
- but in c++ 14, you can use
- for(auto itr : vec)
- extern template
- motivation: currently, in the same translation unit, every instance of the template function will be compiled, even for the same exact type.
//header.h template <typename T> void Foo(){} //source1.cpp #include "header.h" void Bar(){Foo<int>(); } // will compile Foo<int> as an instance //source2.cpp #include "header.h" void Bar2(){Foo<int>(); } // will compile another Foo<int> as an instance, which wastes time.
-
If you know you have the same template function instance IN THE SAME OBJECT FILE (i.e, translational unit), you should use extern
// source2.cpp #include "header.h" extern template void Foo<int>(); // tells compiler somewhere else in the same object file has instantiated this" void Bar2(){ Foo<int>(); }
-
std::vector<std::vector<int>> matrix;
is legal now with>>
-
using
can be used on templates- before C++11, we have
typedef
, we can alias a type, from a template, but not for aliasing a template of templatetemplate<typename T> class MyType{ T u; } // Legal typedef MagicType<int, int> FakeDarkMagic; //type from a template //Illegal template<typename T> typedef MyType<std::vector<T>> NewType; //a template of template, illegal
- in C++11, using is introduced
template<typename T> using NewType = MyType<std::vector<T>> ; //a template, illegal
- before C++11, we have
- default template params
template <typename T = int> void Foo(T t){}
- rvalue & lvalue
- prvalue: pure rvalue. like literals, returned temp variables, lambdas
10; //literals true;
-
Special: string literals in class are Rvalues, in regular functions are lvalues
class Foo{ const char* && right = "this is rvalue"; void Bar(){ right = "still rValue"; } }; void foo(){ const char* left = "this is lvalue"; }
-
Special: string literals in class are Rvalues, in regular functions are lvalues
- xvalue (expiring value, 将亡值), value that can be explicitly moved from
- why const lvauel& can take rvalue? - Cuz fortran needs it
- initializer_list:
#include <initializer_list>
#include <vector>
class MagicFoo {
public:
std::vector<int> vec;
MagicFoo(std::initializer_list<int> list) {
for (std::initializer_list<int>::iterator it = list.begin();
it != list.end(); ++it)
vec.push_back(*it);
}
};
int main() {
// after C++11
MagicFoo magicFoo = {1, 2, 3, 4, 5};
}
- new ctors
- Delegate Ctors
class Foo{ public: int i, j; Foo(): i(100) {} //Foo(int j):j(j), Foo(){} //Error: delegating ctor assumes Foo() initialize all // members, thus it should be the only thing in the initializer list. A general rule of thumb // is to fully define the longest ctor, then delegate other ctors to this ctor }; int main(){ Foo(12); }
- Inheriting ctor: before C++11, derived class will have to copy all its variables to base. With this, there's no more copying
- note: like delegate ctor, here we assume no more members need to be initialize. (cuz they might have been initialized)
#include <iostream> class Base { public: int value1; int value2; Base() { value1 = 1; } Base(int value) : Base() { // delegate Base() constructor value2 = value; } }; class Subclass : public Base { public: using Base::Base; // inheritance constructor int x = 100; // IF YOU HAVE DEFINED NEW MEMBERS: you have to initialize them this way }; int main() { Subclass s(3); std::cout << s.value1 << std::endl; std::cout << s.value2 << std::endl; }
- note: like delegate ctor, here we assume no more members need to be initialize. (cuz they might have been initialized)
-
override and final: to prevent accidentally missing / creating an overloading function.
-
override
cpp struct Base { virtual void foo(int); }; struct Derived{ void foo(int) override{} void foo(float) override{} // illegal, nothing to override }
-
final ```cpp struct Base final{ // yes, final can be used here too virtual void foo() final; }; struct Derived{ void foo(){}; // illegal };
struct Derived2 : private Base{ //illegal, cuz Base is already a "final" class
}; ```
-
delete
anddefault
-
enum class
enum class Foo{
foo1, //note ,
foo2 = 100 // note no ;
};
if (something == Foo::foo2)
- lambda expression
-
params are copied when created, not when used
void lambda_value_capture() { int value = 1; auto copy_value = [value] { return value; }; value = 100; auto stored_value = copy_value(); std::cout << "stored_value = " << stored_value << std::endl; // 这时, stored_value == 1, 而 value == 100. // 因为 copy_value 在创建时就保存了一份 value 的拷贝 }
- each lambda expression has its own unique closure class , but they can be implicitly converted to
std::function
- a lambda can be called like this:
#include <iostream> using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法 void functional(foo f) { // 定义在参数列表中的函数类型 foo 被视为退化后的函数指针类型 foo* f(1); // 通过函数指针调用函数 } int main() { auto f = [](int value) { std::cout << value << std::endl; }; functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值 return 0; }
-
shared_ptr, weak_ptr
-
shared_ptr, reset() will reduce reference count, use_count() will see how many there are ```cpp #include #include using namespace std;
int main() { auto ptr1 = std::make_shared(1); auto ptr2 = ptr1; cout<<ptr2.use_count()<<endl; // show reference count
ptr2.reset(); //reduce reference count by 1 cout<<ptr1.use_count()<<endl; // now reference count is 1
} ```
-
weak ptr: does not involve in reference count, nothing about reference. No * and ->. - meant to solve circular reference,
- cuz you may have
shared_ptr1 -> shared_ptr2, shared_ptr2->shared_ptr1
. - when you want to destruct the two objects owning them, you cannot. - e.g,
#include <iostream> #include <memory> using namespace std; int main() { std::shared_ptr<int> ptr; std::weak_ptr<int> wk_ptr; { auto ptr1 = std::make_shared<int>(1); wk_ptr = ptr1; cout<<wk_ptr.expired()<<endl; //0 ptr = wk_ptr.lock(); } cout<<wk_ptr.expired()<<endl; // still 0, cuz ptr is still alive ptr.reset(); cout<<wk_ptr.expired()<<endl; // 1 }
- cuz you may have
========================================================================
========================================================================
-
decltype(auto)
, for "perfect forwarding" reference of the return type
std::string foo();
std::string& bar();
decltype(auto) lookup(bool f){if (f) return foo(); else return bar(); }
- typename in templates (class cannot be used on dependent type name, typename can).
- dependent type name is type name that depends on another type, like T
- so that's why
class
is "old school"template <class A> void foo(const std::vector<A> vec){ typename std::vector<T>::const_iterator it = v.begin(); // a dependent name on T typedef typename std::vector<T>::const_iterator iter_t; iter_t p2; }
========================================================================
========================================================================
- you can create a temporary variable inside an if, or switch
if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(),3); itr != vec.end()){
*itr = 4;
}
- get things directly from std::tuple
#include <iostream>
#include <tuple>
std::tuple<int, double> f(){
return std::make_tuple<int, double>(1,2.3);
}
int main(){
auto [x,y] = f();
}
- keep the keyword "register", but with no actual meaning
- If something is obsolete, it may still be kept
- constexpr (see SFINAE Alternatives in types.)
- std::is_signed_v, std::is_integral_v are
std::is_signed::value
andstd::is_integral::value
-
std::is_signed_v
(C++ 17)template< class T > inline constexpr bool is_signed_v = is_signed<T>::value
-
-
optional
#include <iostream> #include <optional> using namespace std; int main() { std::optional <int> o{2}; cout<<o.value()<<endl; // return a value here std::optional <int> j; cout<<j.value_or(3)<<endl; // if j doesn't have a value, return 3 j.emplace(100); // emplace helps avoid copy/move construction! cout<<j.value_or(3)<<endl; // if j doesn't have a value, return 3 return 0; }
- optional: no std::optional for reference
-
cpp 17: structural binding:
int arr[] = {1,2,3}; auto [x,y,z] = {1,2,3};
========================================================================
========================================================================
- Designated Initializer
- Definition
struct bar{int x;}; struct foo{int a; char c = 'a'; bar b;}; foo{.a = 1}; //.a is called a designator.
- Wrong examples
foo f1{.a=1, 'b'}; //mix the designator and the non-designator foo f2{.c = 'b', a = 1} //out of order foo f3{.a = 1, .a = 0}; //duplicate initialization foo f4{.b.x = 42; } //nested initialization
- Right examples
foo f1{.a = 1 } //c will remain 'a', b will be {x=0;}, default initialized.
- compilation: https://askubuntu.com/questions/1079885/install-g-9-for-c20
- might need
g++-8 -std=c++2a cpp_20.cpp -fconcepts
- TODO: Exploring C++20 The Programmer’s Introduction pdf
- might need
- Summary
- jthread: raii thread?
- concepts
- Allows the compiler to check template params during compilation.
- e.g
#include <list> # include <algorithm> int main(){ std::list<int> li = {1,2,3}; std::sort(li.begin(), l.end()); // will cause error, cuz sort needs random access iterator, but list doesn't have it }
========================================================================
========================================================================
- variadic args (C)
#include <stdarg.h> #include <stdio.h> void foo(int num, ...){ va_list args; // useful macro va_start(args, num); // set up a pointer to the first arg on the callstack for(int i = 0; i < num; ++i){ // note do NOT change num, there's weird errors! int arg = va_arg(args, int); // need self-promoting types such as int, or double instead of short, or char printf("%d\n", arg); } va_end(args); // gcc clang, optional } int main() { foo(3, 1,2,3,4); // program doesn't know how many args being passed in. }
- C++ 11, variadic template
-
...
has two usages:-
...args
means args is a parameter pack -
args...
means to unpack the parameter pack
-
- for variadic templates, you need to have at least 1 arg
- sizeof...(), not sizeof() (C++ 11)
template<typename... T> void foo(T... args) { std::cout << sizeof...(args) << std::endl; }
-
- In variadic template, placement of
...
is important- unpack recursively
template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) { print(rest...); // recursive call using pack expansion syntax }
-
...
is place differently intempate <typename ...>
andfunc(std::vector<typename>...vec
// v1 is NOT a function parameter pack: template <typename... Types> void func1(std::vector<Types...> v1); // v2 IS a function parameter pack: template <typename... Types> void func2(std::vector<Types>... v2);
- Variadic Template Function evolution
- C++ 11: you need to have a termination function for the recursion
#include <iostream> template<typename T0> void printf1(T0 value) { std::cout << value << std::endl; // This is termination, } template<typename T, typename... Ts> void printf1(T value, Ts... args) { std::cout << value << std::endl; // printing all values but the last one, as that matches the template signature printf1(args...); // ... here is to recursively call this function, or the above function. } int main() { printf1(1, 2, "123", 1.1); return 0; }
- C++ 17: because you have
if constexpr
, which evaluates a condition during compile time, and to instantiate a template function, you need to do that in compile time!#include <iostream> template<typename T0, typename... T> void printf2(T0 t0, T... t) { std::cout << t0 << std::endl; // if (sizeof...(t) > 0) printf2(t...); // not working, since if is runtime! if constexpr (sizeof...(t) > 0) printf2(t...); // working. } int main() { printf2(1, 2, "123", 1.1); return 0; }
- C++ 14 (黑魔法)
- initializer list will ensure sequence, so some ppl will use this to expand args, too.
#include <iostream> #include <vector> template<typename T, typename... Ts> auto printf3(T value, Ts... args) { //deduced std::cout << value << std::endl; auto dummy= { (std::cout << args<<std::endl, 0)... }; } int main() { printf3(1, 2, "123", 1.1); return 0; }
- initializer list will ensure sequence, so some ppl will use this to expand args, too.
- Fold Expression (C++ 17), i.e, operation and
...
at the same time- Simple example: you need () to enclose all this!
#include <iostream> template<typename ...Args> int sum(Args&&... args) { std::cout<< (args + ...)<<std::endl; } int main() { sum(1, 2, 3, 4, 5); return 0; }
- rules:
(E op ...) <=> (E1 op (... op (E N-1 op EN))) (unary right fold) (... op E) <=> (((E1 op E2) op ...) op EN) (unary left fodl) (E op ... op I) <=> (E1 op (... op (EN−1 op (EN op I)))) (binary left fold) (I op ... op E) <=> ((((I op E1) op E2) op ...) op EN) (binary right fold)
- hard to get right
- Alternatives:
- tag dispatching (C++ 11)
template <typename T> void func(T A, std::true_type); template <typename T> void func(T A, std::false_type); template <typename T> void func(T A){ return func(A, std::is_floating_point<T>{}); }
-
std::true_type
are not in any library
- If constexpr (C++ 17)
template <typename T> void func(T a){ if constexpr(std::is_floating_point<T>{}){ //inside brackets, if the condition is met return; } }
- Concept (C++20)
template<class T> concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>; template<SignedIntegral T> void func(T val){ }
-
union in C++:
- all data members share the same data location
- only one member of a union can be accessed at a time
- accessing other data members are illegal
- there's no way to tell which data members are active
- Cannot store std::string, std::vector, reference data types, etc.
- In C it's okay
union pun{ int x; unsigned char c[sizeof(int)]; }; void bad(Pun& u) { u.x = 200; cout<<u.c[0]; // the first byte }
- In C it's okay
-
in C++, reading another data member in union is undefined behavior. This is called type safety: prevents you from using one data member in another type.
- union has bad type safety. So there's "undefined behavior"
- So use reinterpret_cast
int x = 200; auto p = reinterpret_cast<char*>(&x); cout<<p[0];
- in C++ 17, use std::variant
-
std::variant
- introduced in boost in 2004, implemented as an variadic template
- each data member is called an alternative
- only one alternative can hold a value at a given time
- if no alternative is not found using
std::get<type>
, an exception is thrown. Much better than undefined behavior- it will try to find the best type, if type can be transformed
- or nullptr is returned?
-
std::variant::index()
shows the index of the current active value
- When default-constructed, the first element gets default-constructed
- So make sure the first element has a default ctor,
- else, use
std::monostate
, for empty state
- Complete e.g,
#include <iostream> #include <variant> using namespace std; int main() { std::variant<std::monostate, bool, int> var; cout<<var.index()<<endl; // see 0 var = true; cout<<var.index()<<endl; // see 1 cout<<std::get<1>(var)<<endl; //if other alternative is accessed, then will see exception var = 100; cout<<std::get<int>(var)<<endl; cout<<std::holds_alternative<int>(var)<<endl; // does the current variant holds int? cout<<std::holds_alternative<bool>(var)<<endl; // false, cuz now it's int if(std::get_if<int>(&var) == nullptr) // can throw nullptr cout<<"Cannot get ptr to int alternative"<<endl; else cout<<"Got ptr to int alternative!"<<endl; }
- Trap:
#include <iostream> #include <variant> #include <string> using namespace std; int main() { std::variant<std::monostate, bool, std::string> var; var = "holi"; cout<<var.index()<<endl; // see 1, because const char* is resolved into bool, not std::string in C++17. In c++20, it's std::string }
- Errors
- If there's two same types, you will see
no match for ‘operator=’ (operand types are ‘std::variant’ and ‘bool’)
- If no convertible type is found, we will see
no match for ‘operator=’ (operand types are ‘std::variant’ and ‘const char [4]’)
- If there's two same types, you will see