C _Type_Auto - RicoJia/notes GitHub Wiki
========================================================================
========================================================================
- %zu, %lu can be used in printf() to display size_t (note that %zu was added in C89)
- #Include<stdio.h>
- size_t is %lu
- array and ptr are different: array contains size, so works with sizeof. ptr does not.
-
Suppose you have 3 types of inputs:
int i = 1; const int cx = i; const int& rx = i;
- General Rules of thumb:
- The reference-ness in the object being passed in is ignored, during type deduction. You can choose to do T& to add that & back to all objects being passed in. -when deducing for universal references, lvalues will get special treatment
- when deducing by-value params, const and volatile are ignored. (tricky one is const char const ptr)*
- arrays usually decay to pointers, unless they're initialized as references.
- General Rules of thumb:
-
T& or T*
, any reference/pointer type - this is the most regular typetemplate <typename T> void f(T& param){}; f(i); //T will become int, so param will be int&. f(cx); //T will be const int, param will be const int& f(rx); //T will be const int, param will be const int& f(7); //Invalid as we're trying to pass rvalue into f template <typename T> void g(const T& param){}; g(i); //now param is automatically const int, const must be forced template <typename T> void g(const T&& param){}; g(i); //now param is automatically const int&&, const must be forced template <typename T> void p(T* ptr){} //following the same rules as the reference case
- if T already has const, then param will have to be const
- So
const T&&
is a universal reference, not an r-value reference
- So
- if T already has const, then param will have to be const
-
T&&, universal reference,see below for what universal reference is.
template <typename T> void f(T&& param); f(i); //T will become int&, which makes param int& thru reference collapsing f(cx); //param will be const int& f(rx); //param will be const int& f(27); //param will be int&&
- T&& is able to distinguish rvalue and lvalue, and param will turn out to be the corresponding reference type.
-
T as a pass-by-value item
template <typename T> void f(T param){} f(i); //param will be int, easy f(cx); //COUNTER_INTUITIVE: cx will be int, not const int!! f(rx); //COUNTER_INTUITIVE: rx will be int, not const int!! // edge case const char* const ptr = "hola"; f(ptr); //the ptr as an object is a const ptr, so this constness is ignored. But the pointee is of const char, that has nothing to do with the pointer itself. So that constness is preserved. param is of const char
- We are passing in a new object
param
here - The new object param, ignore constness of the original object, because we believe we can modify it.
- We are passing in a new object
- Motivation
- To extract type, with few surprises
- gives the type information in compile time. typeid is in runtime.
- Basic Uses
const int i = 0; // decltype(i) will return const int.
struct Point{
int x, y;
}; //decltype(Point::x) will be int
Point p; //decltype(p) will be Point
//Attention: since operator [] always returns reference,
vec[0]; //decltype will return int&
- So decltype can return & or &&!
- Good Uses
- For declaring variables (C++14)
Widget w; const Widget& w2 = w; auto w3= w2; //w3 is const widget decltype(auto) w4 = w2; //w4 is const Widget&
- Use decltype to deduce return type of functions (and, or lambda)
- C++11 and C++14 has different use cases and forms.
- C++11: form is called "trailing return type"
-
use it on all template functions with "auto" return types
- auto doesn't do type deduction here, it just tells the compiler to watch for trailing return type
- We call it trailing return type because it has "->";
- use it on lambdas which needs to return references
- even work with [] on std::vector
-
use it on all template functions with "auto" return types
- In C++ 14,
- auto in C++14 can deduce return type for the most part, so we don't need it most of the time
- Use to to help auto to return a reference, as auto in return types will ignore reference-ness
- C++11: form is called "trailing return type"
// General: // 1. auto will ignore &, even tho c[i] should always be a reference. So when you want to return a reference, use decltype. // 2. Use std::forward, if user wants lvalue/rvalue. If wants rvalue, we return rvalue reference which can be copied directly. Otherwise, you have an extra copy for building the return value // C++11 template <typename Container> auto access (Container&& c, unsigned int i) -> decltype(std::forward<int>(c[i])) { return std::foward<int>(c[i]); } // C++14 template <typename Container> decltype(auto) access(Container&& c, unsigned int i){ return c[i]; }
- C++11 and C++14 has different use cases and forms.
- get type of a function: you need to pass in the pointer:
decltype(&func)
- std::result_of
- Used to get the type of the return type of a function (deprecated in C++17)
template <typename F, typename...Ts> inline std::future<typename std::result_of<F(Ts...)>::type> trueAsync(F&&f, Ts&&...params){ return std::async(std::launch::async, std::forward<T>(f), std::forward<Ts>(params)...); }
-
EXCEPTIONS
- if an lvalue expression other than a name has type T, decltype will return T&
int i = 3; decltype(i); //return int decltype ((i)); //return int&
-
decltype()
value categories:-
xvalue
, yieldsT&&
-
prvalue
, yieldsT
-
lvalue
, yieldsT&
- so
decltype(1+2)
is int, notint&&
-
decltype(std::declval<T>())>::value)
even thoughstd::declval<T>()
returns an rvalue reference, it's a prvalue ehre
- so
-
-
Basic Uses: comparing types
#include <typeinfo> cout<<std::typeid(var).name()<<std::endl; class Widget; Widget w; cout<<std::typeinfo(w).name()<<endl; // comapring types. struct Base{}; struct Derived : Base {}; cout<<(typeid(Base) == typeid(Derived));
- PKd menas "Pointer to Konst deouble"
- PK6Widget means Pointer to Konst Widget, (widget has 6 chars)
-
Print type inside templated function
template <typename T>
void f(const T& param){
cout<<typeid(T).name();
cout << typeid(param).name();
}
- IDE: good for some basic types, has a compiler running at the background
- Compiler diagnostics: will give you more useful info
template <typename T> class Test; //Anything calls this won't compile because there's no template definition Test<decltype(x)> xType; //So during compile time, we can see the type of this.
- Use typeid to see type in runtime.
- But sometimes, types are still not accurate enough
-
type alias is just a way to alias a type! aTutorial. Will be useful for backward compatibility!
struct Type{ template <typename T> using type = T; }; Type::type<int> i = 5; // "fancy" way to initiate an int.
- it cannot be used to store a type, since a type is not an object
-
std::declval<T>()
, returns an rvalue ref to a temporary T object, so no need to call default ctor, which T may not have#include <utility> #include <iostream> struct Default { int foo() const { return 1; } }; struct NonDefault { NonDefault(const NonDefault&) { } int foo() const { return 1; } }; int main() { decltype(Default().foo()) n1 = 1; // type of n1 is int // decltype(NonDefault().foo()) n2 = n1; // error: no default constructor decltype(std::declval<NonDefault>().foo()) n2 = n1; // type of n2 is int }
========================================================================
========================================================================
-
Motivation
- Came about in C++11, type deduction used to happen in compiler phase, but with auto, it happens in run time.
- means return type will be evaluated in runtime, by deducing from its initializer.
auto a; //compiler error, needs to be initialized auto a = 0;
- means return type will be evaluated in runtime, by deducing from its initializer.
- Came about in C++11, type deduction used to happen in compiler phase, but with auto, it happens in run time.
-
Short summary:
- Auto is essentially the same as template param T below:
template <typename T> void foo(T param); //T is a copy itself, so you need to add const, or & to make it a reference! but constness is preserved.
- Assume you have below vars
int i = 1; const int j = i; const int& k = i;
- Case 1 auto + non-universal reference
template <typename T> void foo (T& param); //equivalent, so const-ness is preserved auto& a = i; //int& auto& a = j; //const int& auto& a = k; //const int & auto& a = 7; //invalid for rvalue auto&& a = 7; //valid, a is an lvalue object, of rvalue type func(std::forward<int>(a)); // You need to forward a to pass it as a real rvalue // The misconceptions: const int&& a = 7; //actually const int&? const auto&& a = 7; // same as above
- Case 2: auto + universal reference
auto&& a = 7; //rvalue auto&& a = i; //lvalue
- Case 3, auto itself creating a copy, like template, constness and volatile are ignored
auto a = j; //ignore const auto a = k //ignore ref. auto func(int& t){ return t; // actually, auto is just like T in template, which will strip off & }
- Auto is essentially the same as template param T below:
-
Exception The only difference from template is in initializer list (C++11 & 14): auto will be deduced into
std::initializer_list<int>
, but template won't and gives you an errorauto a = {1,2,3}; //a is deduced into std::initializer_list bar(a); //valid bar ({1,2,3}); // Template funcs cannot deduce {} into std::initializer_list bar(std::initializer_list<int>{1,2,3}); //valid
- but in lambda, auto is the same as template - it can't deduce
{}
intostd::initializer_list
// auto param auto func = [](const auto& newVal){}; // func({1,2,3}); // can't deduce to {1,2,3} func(std::initializer_list<int>{1,2,3});
- in C++ 14, auto is the same as template function in return type deduction
auto createList = [](){ // return {1,2,3}; // can't deduce to std::initializer_list<int> return std::initializer_list<int>{1,2,3}; };
- but in lambda, auto is the same as template - it can't deduce
-
In C++11, you can use auto for deducing return type of a function, but must be used with decltype:
template <typename A> auto do_something(const A& a) -> decltype(a) { return A; }
- Caution: You should always make sure auto is deduced before the function is used
- auto deduces during compile time.
- But never ever define auto function in another file
- In class members, if another member depends on the type deduced by an auto function, there'll be an error, as data members are declared before auto gets deduced.
- Caution: You should always make sure auto is deduced before the function is used
-
auto&& universal reference param
auto foo = [](auto&& func, auto&& params){};
-
Good uses of auto
- iterator
auto i = c.begin()
- auto can be used to declare a lambda expression as well. It's faster, less fuss
- std::function can store any "callable objects", (class with overloaded(), lambda,etc.), aims to replace function pointer.
- std::function is slower, and takes up more space, because it the object itself takes up fixed amount of memory on stack. If memory is not enough, it will go to heap
- avoid small & hidden errors from explicitly writing things out:
- std::vector::size actually returns std::vector::size_type, which is 64 bits on windows 64 bits, while unsigned int is 32 bits.
std::vector<int> v; unsigned sz = v.size();
- std::vector::size actually returns std::vector::size_type, which is 64 bits on windows 64 bits, while unsigned int is 32 bits.
- iterator
-
you can have auto member variables, but they've to be static const:
struct Timer { static const auto start = 0; };
-
Cautions
- reference needs to be
auto&
, even if it gets a referenceauto& n = fun(); //even if fun() returns int reference, without&, n will be int.
- reference needs to be
- auto a; //needs initialization
- "Invisible Proxy Classes": do not do
auto var = (std::vector<bool>{true, false})[5];
, explicitly force the type instead, see below- Proxy classes mean to emulate the behaviour of a simple thing, like smart pointers.
- Visible ones can be used directly
- while invisible ones, you don't even know that they exist, but you use it as an intermediate product. That's from implicit conversion
void func(bool); auto var = (std::vector<bool>{true, false})[5]; func(var); //Compilation error!
-
usually, operator [] in std::vector will return T&
-
One exception is std::vector will return std::vector::reference, which is an invisible proxy
- It can implicitly convert to bool
- It exists because: std::vector stores a bool in a bit, but C++ forbids reference & to a single bit. So, this is emulating a full 1 byte bool.
- std::vector::reference internally is a pointer to the bit. Since we have a temporary vector, the pointer will become dangling, after this line.
-
invisible proxy classes usually are not compatible with auto.
-
-
Explicitly forcing a type:
bool var = (std::vector<bool>{true,false})[5]; // This is not recommended as it's not crystal clear auto var = static_cast<bool>(std::vector<bool>{true,false}); //This is recommended
========================================================================
======================================================================== casting means to force a data type convert to another.
-
c-style cast: will allow some incompatible casting during runtime but ok in compilation, e.g,
char c = 10; int* p = &c; //okay in compilation, but char is 1 byte, int is 4 bytes, during runtime more bytes will be written and causes error (double)c;
-
static_cast:
-
double to int, unsigned int cast. see code
-
simplest type of casting, happens during compilation, it checks if types are compatible:
int* p = static_cast<int*>(&c);
-
Static Cast also allows converting from & to classes
class Int{ public: Int (int i): i(i){}; //This is a "coversion ctor. " operator std::string(){return std::to_string(i); } //conversion operator, This can be used TO convert to string. int i; } int main(){ Int obj(3); std::string str = obj; //calls conversion operator str = static_cast<std::string>(obj) //conversion operator, from obj to string obj = static_cast<Int>(100); //conversion constructor, from int to Int. }
-
Static Cast can be used in polymorphism as well, but only from derived to base
- derived->base as derived always has base.
class MyDerived : public MyBase{}; MyBase* = static_cast<MyBase*> ptr; //Simplest Usage class MyDerived2 : protected MyBase{}; MyBase* = static_cast<MyBase*> Derived2_ptr; //protected, private child class are not accessible!
- If static cast is used on inheritance, have public instead of private inheritance!!
- DO NOT USE IT ON base->derived casting, as this will be allowed, but might cause run-time errors since the object is incomplete.
- derived->base as derived always has base.
-
Static cast from & to void*
int main(){ int a = 5; const int* ptr = &a; int* ptr_2 = const_cast<int*> (myInt); //without const_cast, there'll be problems. }
-
static_pointer_cast for converting smart pointers
std::unique_ptr<int> u_ptr (new int{1}); *(std::static_pointer_cast<int>(u_ptr)); //Note that we're using std::static_pointer_cast
-
-
Dynamic_cast **This is usually used when but base -> child (downcasting), but it can be used for child -> base class as well. It performance compatibility check in runtime. **You can use it:
- derived -> base
MyBase* = dynamic_cast<MyChild*> (ptr); //can also use static_cast
- base -> derived
//In an array of ptr MyChild* = dynamic_cast<MyChild*> (ptr); //this will succeed if ptr points to a child class object, or it will return **null** if ptr points to a base class object. if(!ptr){...} //**use !ptr here, I am not sure what type this is?**
- base -> derived reference
#include <exception> try{ MyChild &child = dynamic_cast<MyChild&>(*base); } catch(std::exception &e){ cout<<e.what(); }
- Cautions:
- can only be used for base -> child when you ACTUALLY have a child class obj, else you will get a null.
-
const cast It's used for letting a const value be modified. Uses:
- pass const data to function doesn't receive const
but **const cast doesn't allow modify a value initially declared as const, as it's in read-only memory **
int a = 5; const int* ptr = &a; int* ptr_2 = const_cast<int*> (myInt); //without const_cast, there'll be problems.
int a = 5; const int* ptr = &a; int* ptr_2 = const_cast<int*>(ptr); *ptr_2 = 5; //NOT ALLOWED
- Let const member function modify a member variable
class Foo{ public: int i; fun(){(const_cast<Foo*>(this)) -> i = 5; } } const Foo f; f.fun();
- Also, const_cast doesn't allow casting different types
int a1 = 5; const int* b1 = &a1; char* c1 = const_cast<int*>(b1);
- pass const data to function doesn't receive const
-
reinterpret cast
-
Motivation: simply treats one pointer as another, with no checks. So it's dangerous to use. Static_cast does strict type conversion checking, and after conversion, the object is properly converted. Reinterpreted_cast doesn't do that
- Typically only used for converting ptrs of different integral types
int a1 = 40; const char* b1 = static_cast<char*>(&a1); //Don't work as there's no explicitly way to convert them const char* b1 = reinterpret_cast<char*>(&a1); //but char is an integral type, you can actually do this.
- Typically only used for converting ptrs of different integral types
-
Demo only, do not use it:
class Foo{ void fooFunc(); } class Bar{ void barFunc(); } int main(){ Foo* foo_ptr = new(Foo); Bar* bar_ptr = reinterpret_cast<Bar*>(foo_ptr); bar_ptr -> barFunc(); //In this case it compiles, because compilers secretely changes the function signature to Bar::barFunc(Bar* this), then uses this pointer to access the member variables. }
-
Cautions:
- after type_casting, it becomes "non-portable", i.e, you might on other achitectures (ARM, X86), you may run into trouble with the results.
-
-
typeid vs decltype
- typeid(x).name() gives name of the type, declared with auto: i for integer, d for double
- decltype determines type in compile time, so it might gives base class type, while typeid gives derived class type.
-
create temporary for auto (c++17)
auto = std::string("Hello world"); // no temprorary is created.
-
auto , decltype, and initializer_list
auto k = {"str", "lol"}; // copy initialization is allowed with auto. BUT THIS REALLY IS A SPECIAL CASE, AS THE COMMITTEE THINKS auto should still work in this case. decltype({"str", "lol"}i); // Anything other than auto cannot use {} directly, as {} is just an expression, which doesn't have a type
-
Array
- From C, array always decays down to ptrs. Samething happens to template.
- However, we get to preserve the array (i.e, address and size) if we pass it in as a reference
const char name[] = "hola"; // Case 1: array-to-ptr decay template <typename T> void f(T param); f(name); //param will be ptr. // Case 2: preserving the array template <typename T> void g(T& param){} g(name) //param will be a full array!
- Use: useful for getting the size of array during compile time, **combined with constexpr**. which can be used to create other arrays, ```cpp template <typename T, std::size_t N> constexpr std::size_t getArraySize(T(&)[N] param) noexcept{ //constexpr is KEY. noexcept is for generating better code? return N; } //Use int arr[] = {1,2,3,4}; int arr2[getArraySize(arr)]; //getting array size in compile time ```
-
Functions: similar to array,
- functions will decay to function pointer
- ref to function will still be the function!
-
"Incomplete Type": a type that has been declared, but lacks info to determine its size
- structure type declared but no definition for its data members
- union type whose members have not been defined
- array with no defined size.
- if you have
incomplete type
error, maybe you have forward declared the class, but you shouldn't (cuz it exists somewhere already)