Several Things in C++ 2.0
This blog written with 侯捷-C++标准11-14.
Updated at 2023/03/16
1. Variadic Template
It is very convinent to realize recursive function call with variadic template.
void printX() {}
template <typename T, typename... Types>
void printX(const T& firstArg, const Types& ...args)
{
std::cout << firstArg << std::endl;
printX(args...);
}
2. Spaces in Template Expressions
std::vector<std::list<int> > // before C++ 11
std::vector<std::list<int>> // OK now
3. nullptr and std::nullptr_t
nullptr
is a new keyword. It has type std::nullptr_t
.
void f(int);
void f(void*);
f(nullptr); // call f(void*)
// in include\stddef.h
typedef decltype(nullptr) nullptr_t;
4. Automatic Type Deduction with auto
Q: When we use auto
?
A: Where the type is a pretty long and/or complicated experssion. Here is the typical scenarios.
- the type of container’s iterator.
- the type of lambda object.
std::vector<string> v;
auto pos = v.begin();
auto l = [] {
std::cout << ("Hello") std::endl;
}
5. Uniform Initialization
Unify the variable initialization method with initializer_list<T>
- using braces to wrap parameters after a variable.
int i{}; // i is initialized by 0
int* j{}; // j is initialized by nullptr
int x1{5.0}; // ERROR: narrowing; in GCC just warning
char c1{7}; // OK
char c2{99999}; // ERROR: narrowing (not fit for char)
std::vector<int> arr{1,2,3,4}; // OK
std::vector<int> arr(4){1,2,3,4}; // ERROR: expected ‘,’ or ‘;’ before ‘{’ token
6. Initializer Lists
The initialization with initializer_list<T>
is powered by array
in standard template library. In an instance of initializer_list<T>
, there is no data but ptr.
// from https://github.com/microsoft/STL/blob/main/stl/inc/initializer_list
_STD_BEGIN
_EXPORT_STD template <class _Elem>
class initializer_list {
public:
using value_type = _Elem;
using reference = const _Elem&;
using const_reference = const _Elem&;
using size_type = size_t;
using iterator = const _Elem*;
using const_iterator = const _Elem*;
constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {}
constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept
: _First(_First_arg), _Last(_Last_arg) {}
_NODISCARD constexpr const _Elem* begin() const noexcept {
return _First;
}
_NODISCARD constexpr const _Elem* end() const noexcept {
return _Last;
}
_NODISCARD constexpr size_t size() const noexcept {
return static_cast<size_t>(_Last - _First);
}
private:
const _Elem* _First;
const _Elem* _Last;
};
_EXPORT_STD template <class _Elem>
_NODISCARD constexpr const _Elem* begin(initializer_list<_Elem> _Ilist) noexcept {
return _Ilist.begin();
}
_EXPORT_STD template <class _Elem>
_NODISCARD constexpr const _Elem* end(initializer_list<_Elem> _Ilist) noexcept {
return _Ilist.end();
}
_STD_END
Moreover, for a frequently used container vector
, its assignment and insert can also pass in an instance of initializer_list<T>
.
vector<int> a {0,1,2,3};
a = {0,1,2,3,4,5};
a.insert(a.begin()+2, {0,1,2,3,4,5});
max({0,1,2,3,4,5}); // initializer_list can also be the parameter of max and min
min({0,1,2,3,4,5});
7. Explicit ctor
Disallow implicit type casting.
// explicit for ctors taking one argument (before C++ 11)
struct Complex {
int real, imag;
Complex(int re, int im=0): real(re), imag(im) {}
explicit
Complex operator+(const Complex x) {
return Complex((real + x.real),
(imag + x.imag));
}
}
Complex c1(12, 5);
Complex c2 = c1 + 5; // ERROR, no explicit is OK
// explicit for ctors taking more than one argument (C++ 11)
class P {
public:
P(int a, int b) {
std::cout << "P(int a, int b) \n";
}
P(std::initializer_list<int>) {
std::cout << "P(initializer_list<int>) \n";
}
explicit P(int a, int b, int c) {
std::cout << "explicit P(int a, int b, int c) \n";
}
}
P p{77, 5, 42};
P p = {77, 5}; // OK
P p = {77, 5, 42}; // ERROR: converting 'const p' from initializer list would use explicit ...
8. Range-based for
statement
for (const auto& elem: coll) {
std::cout << elem << std::endl;
}
9. =default
\& =delete
Rule of Three (before C++ 11) and Rule of Five (since C++ 11).
Q: What is “Rule of Three”?
A: According to Wiki, the rule of three (also known as the law of the big three or the big three) is a rule of thumb in C++ (prior to C++11) that claims that if a class defines any of the following then it should probably explicitly define all three:
- destructor
- copy constructor
- copy assignment operator
The rule of three claims that if one of these had to be defined by the programmer, it means that the compiler-generated version does not fit the needs of the class in one case and it will probably not fit in the other cases either. For C++ 11, there are two additional functions:
- move constructor
- move assignment operator
Q: When we need to write the functions in the Rule of Three? A: When a class has pointer member then we need to write them by ourselves rather than use the default impl from the compiler.
No-copy and Private copy
// Block the operation copy by =delete
struct NoCopy {
NoCopy() = default; // use the synthesized default constructor
NoCopy(const NoCopy&) = delete; // no copy
NoCopy &operator=(const NoCopy&) = delete;// no assignment
~NoCopy() = default; // use the synthesized destructor other members;
}
// Let friends and members use operation copy
class PrivateCopy {
private:
PrivateCopy(const PrivateCopy&);
PrivateCopy &operator=(const PrivateCopy&);
public:
PrivateCopy() = default; // use the synthesized default constructor
}
10. Alias Template
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>; // using own allocator
Vec<int> coll; // equivalent to
std::vector<int, MyAlloc<int>> coll;
If we want to test a template with different value types, we can use the following methods.
-
function template + iterator + traits (before C++ 11)
With the container template in standard template library, we can get the value type easily with the iterator.
template <typename Container> void test_moveable(Container c) { typedef typename iterator_traits<typename Container::iterator>::value_type Valtype; // get value type through iterator for (long i = 0; i < SIZE; ++i) { c.insert(c.end, Valtype()); } ... } test_moveable(list<MyString>); test_moveable(list<MyStrNoMove>); test_moveable(vector<MyString>); test_moveable(vector<MyStrNoMove>);
-
template template parameter (before C++ 11, since C++ 03)
Note that there is template parameter of template.
template <typename T, template <typename T, typename ALLOC = std::allocator<T>> class Container> class XC1s { private: Container<T> c; public: XC1s() { for (long i = 0; i < SIZE; ++i) { c.insert(c.end, T()); } ... } } XCls<MyString, std::vector> c1;
-
template template parameter + alias template (since C++ 11)
template <typename T, template <typename T> class Container> class XC1s { private: Container<T> c; public: XC1s() { for (long i = 0; i < SIZE; ++i) { c.insert(c.end, T()); } ... } } template <typename T> using Vec = std::vector<T, allocator<T>>; XCls<MyString, Vec> c1;
12. Type Alias, noexcept
, override
, final
Type alias is similar to typedef
.
using func = void(*)(int, int);
void example(int, int) {};
func fn = example; // OK
Clarification on the usage of using
.
-
using-directives for namespaces and using-declarations for namespace members;
using namespace std; using std::count;
-
using-declerations for class members; (
using std::count;
inner class); -
type alias and alias template declaration (Since C++ 11).
nonexcept
noexcept
indicates that a function will not throw any exception. And conditions can be attached.
void swap (Tpe& x, Tpe& y) noexcept(noexcept(x.swap(y))) {
x.swap(y);
}
// Then the move constructor will be called whenthe vector grows.
// If the constructor is not noexcept, std::vector can't use it.
// Growable conatiners includes vector and deque (possibly make memory reallocation).
class MyString {
private:
char* _data;
size_t _len;
...
public:
//move constructor
MyString(MyString&& str) noexcept: _data(str._data), _len(str._len) {...}
//move assignment
MyString& operator=(MyString&& str)noexcept {... return *this;}
}
override
Inform the complier that I override some virtual function in the parent class.
virtual void vfunc(float) override {};
final
Inform the complier that this class will not have any derivated class anymore or this function will not be overrided anymore.
virtual void vfunc(float) final {};
13. decltype
It can be used to satisfy the demands to typeof
. It has different application scenarios.
-
declare return types;
template <typename T1, typename T2> auto add(T1 x, T2 y) -> decltype(x+y);
-
in metaprogramming;
template <typename T> void test18_decltype(T obj) { std::map<std::string, float>::value_type elem1; std::map<std::string, float> coll; decltype(coll)::value_type elem2; }
-
pass the type of a lambda.
auto cmp = [] (const Person& pl, const Person& p2) { return pl.lastname()<p2.lastname() || (pl.lastname() == p2.lastname() && pl.firstname()<p2.firstname()); } std::set<Person, decltype(cmp)> coll(cmp);
14. lambda
[...] (...) mutable throwSpec -> retType {...}
| | | | | |
| | opt opt opt body
| parameters (if one of the following three occurs, () are mandatory)
lambda introducer
[&]
to pass all by references
[=, &y]
to pass y
by reference and all other objects by value
Note that lambda do not have the default constructor function and assigment operator.