Learning to be Giant.

C++ Learning Notes III

|

This is the 3rd article in this series. I planned to finish reading 100 pages a day, but failed today. I will try making it up tommorrow

initializer_list

The new standard provides a way for functions to accept unspecified number of parameters. If all the parameters are of the same type, initializer_list can be made use of.

void func(initializer_list<string> lst) {
    for (auto i : lst) {
        // something
    }
    
    for (const auto &i : lst) {
        // something
    }
} // define
func({"hello", "world"}); // call

The old-style ... and varargs thing should only be used for types that are common for both C and C++, since objects of most class types are not copied properly when using ....

NOTE: the elements in a initializer_list are always const.

Return a list initialization

In the new standard, one can return list initializers as a return value. If the function returns a built type, there can be only ONE element in the curly braces and the element cannot lose precision when conversion needed:

int func() {
    return {0}; // OK
}

int func() {
    return {1.1}; // Error
}

if the return type of a function is an class type, the class defines how the initializers are used:

vector<string> func() {
    return {"hello", "world"};
}

Trailing return type and decltype

See here first.

Besides the above way to resolve the problem, we can actually use a new way provided by the new standard.

auto func(int i) -> int (*)[10];

This func will take in an int and return an pointer to an array of 10 elements of int.

In addition, we can also use the following syntax:

int odd[] = {1,3,5,7,9};
int even[] = {2,4,6,8,0};

decltype(odd) *arrPtr(int i) {
    return (i % 2) ? &odd : &even;
}

I have to say, this is a really tricky stuff.

constexpr functions

This kind of functions can be used in a constant expression. The limit of such functions is that there should be only ONE return statement in the function and the return type and parameter type must be literal type.

Aconstexpr function is not required to return a constant expression.

For example:

constexpr int func(int i) {return i * 2}

int arr[func(2)]; // ok, since the return value is a const expression
int i = 2;
int arr[func(i)]; // error, func(i) is not a const expression

Meanwhile, a constexpr function is automatically inline.

Function overload and how to resolve the best function matching

  • The match for each argument is no worse than the match required by any other viable function
  • There is at least one argument for which the match is better than the match provided by any other viable function

friend

This concept is essential but I didn’t make it clear when I first learned it.

Some methods in a class is protected by the access control, and cannot be accessed by other classes or functions. To enable other classes or functions to access these members, one should declare the function / class in that class:

void print(const Class &c) {
    std::cout << c.member << std::endl;
}

class Class {
friend void print(const Class&);
private: 
    int member = 0;
}

To make a friend visible to users of the class, we usually declare each friend (outside the class) in the same header as the class itself.

mutable

mutable class members are those members that are never const, even though the class object is const. For example, if we define a list, and there is a indicator of the current position. The indicator can change when traversing the list, but the data members of the object remains unchanged:

class List {
private:
    mutable int idc = 0;
    int data[10] = {1,2,3,4,5,6,7,8,9,0};
public:
    int current() const {
        return data[idc];
    }
    void next() const {
        idc = (idc + 1) % 10;
    }
    // other methods are omitted
};

void test(const List & lst) {
    cout << lst.current() << endl;
    lst.next();
    cout << lst.current() << endl;
}

int main(int argc, char const *argv[])
{
    List lst;
    test(lst);
    return 0;
}

/* OUTPUT:
 * 1
 * 2
 */

Misc.

  1. static objects

    static objects will be initialized before the first time execution passes through the object’s definition. Local statics are not destroyed when function ends; they are destoryed when the program terminates.

  2. Use reference to const when possible
  3. When passing multidimensional array as parameter, one should specify the length of each dimension except the first.

    cpp void print (int matrix[][10]);

  4. Reference returns are lvalues cpp T& func(); T X; func() = X;
  5. How to declare a function that returns a pointer to an array?

    This one is especially tricky.

    cpp int arr[10]; // an array of 10 elements int *arr[10]; // an array of 10 pointers to int int (*arr)[10]; // a pointer to array of 10 elements

    therefore,

    cpp int (*func(int))[10]; // a function named func, which accepts an int as parameter and returns a pointer to an array of 10 int elements

  6. inline and constexpr functions should be defined in the header file for multiple usages across files. (they should be defined multiple times)
  7. Some preprocessor thing
macro/variable description
assert(expr) if the expr is false, error messages are outputted and the program terminates
NDEBUG when NDEBUG is defined, assert will not be executed. It's a indicator of whether the program is under debug mode
__func__ this is a local static variable. the variable holds the name of a function. This is **NOT** a macro
__FILE__ macro - name of the file
__LINE__ macro - line of the code
__TIME__ macro - compile time
__DATE__ macro - compile date
  1. function pointer stuff

    cpp // Func and Func2 have function type typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2; // equivalent type // FuncP and FuncP2 have pointer to function type typedef bool(*FuncP)(const string&, const string&); typedef decltype(lengthCompare) *FuncP2; // equivalent type

  2. The new standard supports “in-class initializer”. The generated default constructor will use the “in-class initializer” to initialize the member varibles.

    cpp class Class { int member = 0; }

    Meanwhile, another thing that worth notice is that if we defined any constructor, the compiler will not generate the default constructor for us.

    Another thing that worth your attention is that in the new standard, a = default syntax is added. Therefore, if you want to define some other constuctors while keeping the default generated constructor, you can do:

    cpp class Class { private: int member = 0; public: Class() = default; Class(int i) { member = i;} }

    Besides, we can do this:

    cpp class Window_mgr { private: // Screens this Window_mgr is tracking // by default,a Window_mgr has one standard sized blank Screen std::vector<Screen> screens{Screen(24, 80, ’ ’)}; };

Comments