打开导航 打开导航 Open menu
量化交易系统开发(C++)/ 期权策略与风控 / 行业研究分析
量化交易系统開發(C++)/ 期权策略与风控 / 行业研究分析
C++ trading systems / options strategy & risk controls / equity research
adrian@adrianxv.cn
文章目录

auto in C++ auto in C++ auto in C++

This is a new series of notes/blogs, something I wrote when learning Daily C++ Interview by Sandor Dargo. And I try to just take notes in English because I want to stop translating in my mind.

The benefits of using auto

  1. Saves time from typing verbose types and improves correctness
  • long user defined types
  • types of lambdas (which cannot be expressed)
  1. Force definition with declaration
  • Declaration tells the compiler that there is a variable of certain type or a function with certain parameters and return types.
  • Definition allocates memory for variables or provides implementation for functions.

This is declaration with no definition, because the extern keyword states that the the definition of the current variable/function is in other .cpp files (known as translation unit files):

extern int x;

This is declaration with definition. a is explicitly defined, whereas b is implicitly defined (with a value of 0):

int a = 10;
int b;

With auto, you have to define a variable when you declare it, because otherwise the complier cannot deduce it’s type.

  1. No need to change type when refactoring/maintenance

using auto with references and pointers

auto can deduce pointer types. Here p is type of int*

int* int_pointer;
auto p = int_pointer;

But auto cannot deduce reference types. Here r is type of int, not int&.

int i = 1;
int& int_reference = i;
auto r = int_reference;

So in order to get a reference type, we have to explicitly write auto&. We might as well just write auto* for pointer types for clearity.

int i = 5;
auto* p = &i;
auto& r = i;

using auto with const

We shall first review the use of const with pointers:

Here we cannot modify i through p:

int i = 5;
const int* p = &i;

Here we cannot change what variable p points to:

int i = 5;
int* const p = &i;

Here we prevent both operations:

int i = 5;
const int* const p = &i;

While we may have 2 variations of using const with a pointer, there is only one way of using const with reference. By doing so, you cannot modify the variable through the reference and you cannot bind the reference to another variable either.

int x = 5;
const int& r = x;

And if we use auto in such a case, the constness of the pointer itself is discarded. But you still cannot modify i through ap.

int i = 5;
const int* const p = &i;
auto ap = p;

Similar cases happen in all copy assignment scenarios, where the constness of the copied-from object is discarded, because we are creating a brand new object:

const int x = 5;
int y = x; // y is int, not const int

Next, we have auto&&, which is a forwarding reference that follows reference collapsing rules:

int x = 5;
auto&& a = x;             // type of int&
const int y = 5;
auto&& b = y;             // type of const int&
auto&& c = 5;             // type of int&&
auto&& c = y;             // type of const int&&

auto vs templates in type deduction

auto type deduction and template type deduction are the same except that auto can deduce the type std::initializer_list<T> from brackets {}, whereas templates cannot.

auto x = {1, 2}; // x is type of std::initializer_list<int>
auto y = {}; // Compilation Error. Not an empty container

In C++, the brackets {} themselves does not necessarily mean initializer lists. It can mean a lot of stuff. The ability to deduce std::initializer_list<T> is just a feature of auto, which templates lack.

However, templates can deduce the type for T in the following scenario:

template<typename T>
void foo(std::initializer<T> x) {
    ...
}

foo({1, 2, 3}); // T is deduced to be ints

problems with using auto with std::vector

std::vector<bool> stores bits rather than boolean objects. And if we subscript a std::vector<bool> and save the results with auto and later use that result, we get undefined behavior. It’s because result is a pointer to a temporary object (which is implementation-dependent).

std::vector<bool> foo();
auto result = foo();
if (result[5]) { // undefined behavior
    // ...
}

We can static_cast the result into bool and use it.

std::vector<bool> foo();
auto result = foo();
if (static_cast<bool>(result[5])) {
    // ... OK
}

trailing return types

Trailing return types allows for using auto for the return type, and later declaring the return type of a function after its parameter list:

auto foo(...) -> int {
    // ...
}

This helps when the return type is defined in a namespace. We can omit the verbose namespace in the return type. The examples below show the definition of foo with and without trailing return types:

namespace VerboseClass {
    struct myStruct {
        // ...
    }
}

Instead of writing:

VerboseClass::myStruct VerboseClass::foo() {
    // ...
}

We can write:

auto VerboseClass::foo() -> myStruct {
    // ...
}

Because at the beginning of the line, the compiler cannot know the scope (the class name). But since the function name is VerboseClass::foo(), the compiler knows about the class name afterwards.