Language Usability Enhancements

Language Usability Enhancements

Reference: https://github.com/changkun/modern-cpp-tutorial

  • Language behaviour that occurred before the runtime.

Constants

nullptr:

  • CPP does not allow implicit conversion of void * to other types.
  • Traditional CPP treats null and 0 same.
  • So to certain how to deal with NULL in cpp, use nullptr
  • By doing so we can do the right function calls.

constexpr:

  • Expression produces the same result without any side effects.
  • Compiler will optimise and embed it at compile time.
  • It is useful as CPP array length must be constant expression; yet most compilers these days still support as part of its optimisation.
  • Therefore, use constexpr for array length so that it can be compiled.
1
2
3
4
// Below is C++11 form, from 14, you can write like a function.
constexpr int fib(const in n){
return n == 1 || n == 2 ? 1 : fib(n-1) + fib(n-2);
}

Variables and Initialisation

IF Statement
In C++17, following is available like Go

1
2
3
4
5
6
7
8
// Original
auto itr = std::find(vec.begin(), vec.end(), 2);
if (itr != vec.end()) *itr = 3;

// CPP17 - can define a local variable, and check the condition
if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()){
*itr = 4;
}

Initialiser_List

  • It is the one that allows to initialise the different type with same syntax: {} introduced in C++11
  • Auto used in followings:
    • Used with {} at Initialisation and assignment expression.
    • Binded to auto including ranged for.
  • It can be implemented with 2 ptr, or 1 ptr and size.
  • Be aware that it is a shallow copy which does not guarantee the data for the original instance deleted.
  • Possible errors in narrowing conversion like int to double.
  • Which means we can use itr for array.

Structured Binding

  • Provide functionality similar to the multiple return values
  • Use std::tuple to return multiple values in tuple form.
  • But, unfortunately, we have to clearly define how many values the returned tuple has.
1
2
3
4
5
6
std::tuple<int, double, std::string> f(){
return std::make_tuple(1, 2.3, "45");
}
...
// auto will be applied indiv to each variables.
auto [x, y, z] = f();

Type Inference

auto

  • Cannot be used for function arguments.
  • Cannot be used to derive array types: auto arr[10] = arrOrigin; // cannot infer array type.

decltype

  • Used to solve the defect that the auto keyword can only type that variable -> use expression!
    1
    2
    3
    4
    auto x = 1; // int
    auto y = 2.1; // double
    decltype(x+y) z; // double
    z = 1; // z is still double = 1.0.

tail type inference

Quick Note: In specifying a template (naming), class and typename are interchangeable = equivalent. However: special cases are

  • In the case of dependent types. typename is used to declare when referencing a nested type that depends on another template parameter such as typedef as following.

  • When you declare a template template parameter: must use class.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Hint at the compiler that you are referring to a dependent type
    template<typename param_t>
    class Foo{
    typedef typename param_t::baz sub_t;
    };

    // declaring a template template
    template <template class T> class C {}; // valid!
    template <template typename T> class C{}; // Invalid!

    tail type inference: use decltype in specifying template.

  • Since the compiler will not know which type is the input parameter, decltype must be defined as a tail to define template’s return type as following:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // C++11
    template<typename T, typename U>
    auto add(T x, U y) -> decltype(x+y){
    return x+y;
    }

    // C++14
    template<typename T, typename U>
    auto add(T x, U y){
    return x+y;
    }

    // use link
    auto w = add<int, double>(1, 2.0); // return is 3.0 as double.

decltype(auto)

  • It deals with the concept of parameter forwarding in C++, will cover later.
  • Mainly to derive the return type of a forwarding function or package, which does not require us to explicitly specify the parameter expression of decltype.
  • 쉽게 말하면, 그냥 리턴타입 귀찮은거 알아서 해준다는 소리.

Control Flow

if constexpr in C++17

  • 간단하게 if statement 에서 constexpr 사용할 수 있게되었다.
1
2
3
4
5
template<typename T>
auto func(const T& t){
if constexpr (std::otherFunc<T>::value) do something;
else do somethingElse;
}

Templates

Philosophy: throw all problems at compile time so that only deals with core dynamic services at runtime; optimise the performance of the run time.

Templates

  • When compiler sees fully defined template, it will instantiate it in each compilation unit.
  • 만약 정의만 하고 사용하지 않는다? 컴파일 되지도 않음 (디버깅 하면 함수가 존재도 안함) -> 이 이유로 다른 함수처럼 헤더에 선언만 하면 안됨. 어디든 정의도 같이.
  • 만약 더하기 함수를 정의하고 int 와 double 두 가지 자료형으로 사용한다면 -> 컴파일 후 해당 템플릿의 int, double 두가지 형식의 함수가 생김.

Extern Templates

  • By using extern, we can explicitly tell the compiler when to instantiate the template.
  • 만약 다른 컴파일 유닛에 해당 템플릿과 동일한게 존재 한다면 (같은 자료형을 받는), 한곳에 extern 을 사용하여 해당 템플릿이 단 한번만 컴파일 되게 지정한다 (성능 향상)
1
2
template class std::vector<bool>; // force instantiation
extern template class std::vector<double>; // Should not instantiate in current file.

Type alias templates & Default template Parameter

  • Template is not type so cannot use typedef, but we want to make a certain type for a template like followings
  • You can set default type for template parameter like a function
1
2
3
4
5
6
7
8
9
10
// Template Type Alias
template <typename T>
using soModern = someType<std::vector<t>, std::string>;
int main(){
soModern<bool> isIt;
}

// Default template parameters
template <typename T = int, typename U = int>
auto add(T x, U y) -> decltype(x+y){return x+y;}

Variadic Templates - 가변 길이 템플릿

  • 말 그대로 가변으로써 여러 패러미터들을 따로 명시하지 않고 … 처럼 벌크로 넘길 수 있는 방식.
  • bulk 로 주어진 args 를 unpack 하는 방법에는 보통 다음과 같은 형태를 따른다.
  1. Recursive Template Function
  • Most classic approach that we can think of.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename TO>
void printf1(TO value){
cout << value << endl;
}

template<typename T, typename... Ts>
void printf1(T value, Ts... args){
cout << value << endl;
printf1(args...); // when # args = 1 -> trig above func
}

int main(){
printf1(1,2,"asdf", 1.1);
return 0;
}
  1. Variable parameter template expansion
    Very cumbersome - can use std::bind

    1
    2
    3
    4
    5
    tempalte<typename TO, typename... T>
    void printf2(TO t0, T... t){
    std::cout << t0 << std::endl;
    if constexpr (sizeof...(t) > 0) printf2(t...);
    }
  2. Initialise list expansion
    Recursive Functions are a standard practice -> drawback is that you must define a terminate function.

1
2
3
4
5
6
7
8
template<typename T, typename... Ts>
auto printf3(T value, Ts... args){
std::cout << value << std::endl;
// Lambda Expression
(void) std::initializer_list<T>{([&args])}{
std::cout << args << std::endl;
}(), value)...};
}

Fold expression

1
2
3
4
// Available from C++17
template <typename ... T>
auto sum(T ... t) return (t + ...);
int main(){std::cout << sum(1,2,3,4,5) << std::endl;}
  • Inheritance constructor: using Parent::Parent; // Inherit parent constructor.

Explicit Virtual function overwrite

1
2
3
4
5
6
7
8
9
struct Base {
virtual void foo(); //abstract function - will be overriden by child object.
};

struct Child : Base {
// Can be two options: Override parent func | Add the same name func.
void foo();
// If base foo() gets deleted -> Child has its own method -> undefined behaviour.
};

To Avoid above situation C++11 introduced override and final.

Override

  • explicitly tells the compiler to overload -> compiler check the base func if it has a virtual function, otherwise throw error: virtual void foo() override;

Final

  • To prevent the class inherit and to terminate the virtual function to be overloaded.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct Base{
    // No one can inherit & overload this function (so no same name).
    virtual void foo() final;
    };

    // No one can inherit this Child class.
    struct Child final : Base {
    // Legal, but Child cannot be parent because of final.
    };

Delete and default

As discussed, compiler generates default constructor, destructor, and copy assignment if not defined.

But if you want to prohibit certain behaviour, it must be defined in private -> not comfty.

By using default and delete, you can remain it in the public scope like following:

1
2
3
4
5
class Base {
public:
Base() = default; // Telling compiler to use default generated ctor.
Base& operator=(const Base&) = delete; // to refuse constructor copy assignment (but remain it in the public)
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×