# cpp17

## go home! [:house\_with\_garden:](https://github.com/wnsgml972/midas_log)

## C++ 17 문법 공부

### 사용 법

**Visual Studio 2017 기준에서 C++17 컴파일러를 이용하려면** \
&#x20;**C/C++ > 언어 > C++ 언어 표준에서 다음과 같이 선택하면 된다.**

![c++17](https://4041703324-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LczQ5sDwXTyA6y83Msj%2Fsync%2F420eee47256d7b2be28cf5d95c148b4055fa840e.png?generation=1593336554902113\&alt=media)

### if/Switch Vaild

if/Switch 문 하나에서 초기화 작업과 검증 과정을 동시에 할 수가 있음!

```cpp
// if
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> *v;

//    if(initializing; Validation) 한번에 가능
    if (v = nullptr; v->size() == 0)
    {
        // Using v
    }
}

// switch
// C++ 17. "res" 변수는 switch 절 scope를 가진다.
switch (auto res = writePacket(); res.second)
{
    case SUCCESS:
        std::cout << "successfully wrote " << res.first << " bytes\n";
        break;
    case DEVICE_FULL:
        std::cout << "insufficient space on device\n";
        break;
    case ABORTED:
        std::cout << "operation aborted before completion\n";
        break;
}
```

### Structured Bindings

Cpp Basic tuple에서 데이터를 얻은 방법으로서 `[]` 를 이용한 문법이다.

#### 예시 1

밑의 코드를 보면 i에는 x, s에는 str이 받아진다. struct 변수를 직접 `auto [i, s]`를 통해 변수를 받고 변경하면, 결과값은 바뀐 결과값으로 출력된다.\
i와 s가 값이 바뀌는 것은 참조와 관련이 있기 때문이다. (& 는 없지만), 허나 참조와 완전히 같지는 않고 비슷한 방식으로 동작한다고 한다.

```cpp
struct Foo
{
    int x = 0;
    std::string str = "world";


    ~Foo() { std::cout << str; }
};

int main()
{
    auto[i, s] = Foo();
    std::cout << "hello ";
    s = "structured bindings";

    // 결과로 hello world가 아닌
    // hello structured bindings  출력
}
```

#### 예시 2

위의 코드에서 헷갈렸던 참조를 이 예제로 정리할 수 있다!\
case 1과 case 2를 보면 된다. x를 참조가 아닌 값으로 **객체를 생성하면서 받지 않고!** 받을 경우 a를 변화시켜도 실제 값인 x의 i값은 변하지 않는다.\
허나 case 2를 보면 객체를 생성하면서 값을 받을 경우 b의 값이 곧 생성된 객체의 값과 같다.\
이제 case 5를 보면 참조로 받은 경우 밖에서 변화된 값이 x.i의 값과 같은 것을 확인할 수 있다.\
case 3은 const와 관련이 있다.\
case 4는 참조를 통해 새로 생성된 형태의 객체를 받을 수 없다.

```cpp
#include <iostream>

int main()
{
    struct X { int i = 0; };

    X x;

    // case 1  O
    auto[a] = x;
    a++;
    std::cout << a << x.i << std::endl;
    // a = 1, x.i = 0

    // case 2  O
    auto[b] = X();
    b++;
    std::cout << b << std::endl;
    // b = 1

    // case 3  △
    auto const[c] = X(); // Build Warning!  i가 const가 아니기 때문에
    //c++; Copile Error

    // case 4  X
    //auto &[d] = X(); Compile Error
    //d++;

    // case 5  O
    auto &[e] = x;
    e++;
    std::cout << e << x.i << std::endl;
    // e = 1, x.i = 1

    // case 6  △
    auto const &[f] = X(); // Build Warning!  i가 const가 아니기 때문에
    //f++;  Compile Error
}
```

#### 예시 3

배열에도 다음과 같이 사용할 수 있다.

```cpp
#include <iostream>

int main()
{
    int arr[4] = { 1, 2, 3, 4 };
    auto[a, b, c, d] = arr;
    //auto&[a, b, c, d] = arr;

    a = 3; //마찬가지로 주석의 방법을 사용하지 않을 경우 값이 바뀌지 않음

    for (auto p : arr)
    {
        std::cout << p;
    }
}
```

### Deduction Guides

이것으로 인해 지금까지 템플릿에서 타입을 명시해서 생성해줘야 했던 것들이 아래와 같이 사용 가능하다.(컴파일러가 Type을 추정 함)\
막 귀찮게 make\_tuple 이런 거 안해도 됨

```cpp
#include <iostream>
#include <tuple>
#include <string>



int main()
{
    std::tuple<int, std::string> is1 = std::tuple(17, "hello");
    auto is2 = std::tuple(17, "hello"); // !! pair<int, char const *>
    auto is3 = std::tuple(17, std::string("hello"));
    auto is4 = std::tuple(17, "hello");
}
```

### template auto

다음과 같이 Type을 auto로 지정할 수 있다.

```cpp
template <auto v>
struct integral_constant
{
    static constexpr auto value = v;
};
int main()
{
    integral_constant<2048>::value;
    integral_constant<'a'>::value;

}
```

### Fold Expressions

다음과 같이 sum(a,b,c,etc...) 같은 형태를 다음과 같이 표현할 수 있다.

```cpp
template <typename... Args>
auto sum(Args&&... args) {
    return (args + ... + 0);
}


int main()
{
    sum(4, 5, 6, 7);
}
```

### Nested Namespaces

중복 namespace를 다음과 같이 표현할 수 있다.

```cpp
namespace A::B::C {
   struct Foo { };
   //...
}
```

### Single Param static\_assert

**static\_assert은 컴파일 타임에 assert를 검사해준다. 런타임 상수가 아닐 경우, static\_assert를 이용하여 컴파일 타임에 검사를 진행하도록 하자.**

* static\_assert를 하나의 인자로 사용할 수 있다.

```cpp
static_assert(sizeof(short) == 2)
```

### Inline Variables

다음과 같은 문법도 사용 가능하다.

| C++14 | C++17 |
| ----- | ----- |
|       |       |

| C++14 | C++17 |
| ----- | ----- |
|       |       |

### constexpr 선언

**사실 C++ 에는 두 가지 다른 종류의 상수가 있다.** \
\
&#x20;**런타임 상수(runtime constant)는 초깃값을 런타임에서만 확인할 수 있는 상수다.** 아래 예제에서 `age`는 컴파일러가 컴파일 시 값을 결정할 수 없으므로 런타임 상수다. `age`는 런타임 때 사용자의 입력에 의해 달라질 수 있어 컴파일 타임에는 값을 결정할 수 없다.\
\
&#x20;**컴파일 시간 상수(compile-time constant)는 컴파일 시간에 초깃값을 확인할 수 있는 상수다.**

#### const 선언은 런타임 시간 상수도 허용을 하는 상수 선언 법이다.

#### define을 이용하여 심볼릭 상수를 이용하는 것은, 디버거에 나타나지 않으므로 좋지 않다. const나 constexpr을 이용하여 상수 선언을 하도록 하자.

```cpp
#include <iostream>

int main() {
    // compile 시에는 실행하기 전 값이 확실히 초기화 되어 있어야 함
    constexpr double gravity(9.8); // ok, the value of 9.8 can be resolved at compile-time 
    constexpr int sum = 4 + 5; // ok, the value of 4 + 5 can be resolved at compile-time 
    std::cout << "Enter your age: "; 
    int age;
    std::cin >> age;
    constexpr int myAge = age; // not okay, age can not be resolved at compile-time

    // run-time 시에는 값이 정해져 있다면 다 됨!
    const double gravity1(9.8);
    const int sum1 = 4 + 5;
    std::cout << "Enter your age: ";
    int age1;
    std::cin >> age1;
    const int myAge1 = age1;
}
```

### `std::optional<A>`

**결과와 반환 값을 한번에 컨트롤 할 수 있게 해준다.**

```cpp
#include <string>
#include <iostream>
#include <optional>

// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b) {
    if (b)
        return "Godzilla";
    return {};
}

// std::nullopt can be used to create any (empty) std::optional
auto create2(bool b) {
    return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}

// std::reference_wrapper may be used to return a reference
auto create_ref(bool b) {
    static std::string value = "Godzilla";
    return b ? std::optional<std::reference_wrapper<std::string>>{value}
    : std::nullopt;
}

int main()
{
    // example 1. 
    std::cout << "create(false) returned "
        << create(false).value_or("empty") << '\n';


    // example 2.
    // optional-returning factory functions are usable as conditions of while and if
    if (auto str = create2(true)) {
        std::cout << "create2(true) returned " << *str << '\n';
    }
    else {
        std::cout << "create(false) returned "
            << create2(false).value_or("empty") << '\n';
    }

    // example 3.
    if (auto str = create_ref(true)) {
        // using get() to access the reference_wrapper's value
        std::cout << "create_ref(true) returned " << str->get() << '\n';
        str->get() = "Mothra";
        std::cout << "modifying it changed it to " << str->get() << '\n';
    }
}
```

### std::variant

`형식에 안전한(typesafe) union`**,** `variant`

* 밑의 그림과 같이 컴파일 시점에 틀린 부분을 점검해주기 때문에 형식에 안전한 공용체라 불린다.

```cpp
void f()
{
    variant<int, double, string> v = 123;

    cout << v << endl; // 123

    cout << std::get<int>(v) << endl; // 명시적으로 int를 요청.
                                      // 앞의 행과 같은 결과를 낸다.

    cout << std::get<0>(v) << endl;   // 첫 번째 형식(int)의 값을 요청.
                                      // 역시 같은 결과를 낸다.

    auto f = std::get<float>(v);      // (1) 컴파일 오류

    auto cond = std::get<string>(v);  // (2) 실행 시점 예외 발생
}
```

> visit? \
> &#x20;이 부분은 variant의 활용에 중요하지만 지면 제한 때문에 간단하게만 언급했는데, 언제 std::variant와 방문자 패턴, 그리고 '꼬리표 달린 공용체(tagged union)'에 관해 보충 글을 써보겠습니다.

* 진짜 핵 어려움..

```cpp
#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

// the variant to visit
using var_t = std::variant<int, long, double, std::string>;

// helper type for the visitor #3
template<class T> struct always_false : std::false_type {};

// helper type for the visitor #4
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;

int main() {
    std::vector<var_t> vec = { 10, 15l, 1.5, "hello" };
    for (auto& v : vec) {

        // 1. void visitor, only called for side-effects (here, for I/O)
        std::visit([](auto&& arg) {std::cout << arg; }, v);

        // 2. value-returning visitor, demonstrates the idiom of returning another variant
        var_t w = std::visit([](auto&& arg) -> var_t {return arg + arg; }, v);

        // 3. type-matching visitor: a lambda that handles each type differently
        std::cout << ". After doubling, variant holds ";
        std::visit([](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "int with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, long>)
                std::cout << "long with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, double>)
                std::cout << "double with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "std::string with value " << std::quoted(arg) << '\n';
            else
                static_assert(always_false<T>::value, "non-exhaustive visitor!");
        }, w);
    }

    for (auto& v : vec) {
        // 4. another type-matching visitor: a class with 3 overloaded operator()'s
        std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
            }, v);
    }
}
```

### std::any

`형식에 안전한(typesafe) void*`**,** `any`

* 복사가 가능한 형식만을 담을 수 있다.
* `any_cast`
* optional과 같이 빈 객체를 생성하는 기본 생성자가 있다. 즉 `has_value() false, true 가능하다`

```cpp
#include <any>
#include <iostream>

int main()
{
    std::cout << std::boolalpha;

    // any type
    std::any a = 1;
    std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
    a = 3.14;
    std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
    a = true;
    std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';

    // bad cast
    try
    {
        a = 1;
        std::cout << std::any_cast<float>(a) << '\n';
    }
    catch (const std::bad_any_cast& e)
    {
        std::cout << e.what() << '\n';
    }

    // has value
    a = 1;
    if (a.has_value())
    {
        std::cout << a.type().name() << '\n';
    }

    // reset
    a.reset();
    if (!a.has_value())
    {
        std::cout << "no value\n";
    }

    // pointer to contained data
    a = 1;
    int* i = std::any_cast<int>(&a);
    std::cout << *i << "\n";
}
```

### std::string\_view

변하지 않는 문자열 값을 전달할 때 우리는 `const string& val`을 이용했다. 참조 const를 이용하므로서 메모리를 새로 할당하지 않는 것이다. 하지만 string을 전달하지 않고 `"Hello World"`로 전달할 때는 const char\*를 받는 생성자를 호출함으로서 메모리를 새로 할당한다.

그에 대한 대체제로 `std::string_view`가 나왔다. 이제 우리는 `const string_view& val`을 통해 값을 전달하면 된다.

```cpp
void println(const string_view& str)
{
    cout << str << endl;
}
```

### 기타 새로 추가 된 알고리즘

**C++17에서 새로 추가된 알고리즘들을 간략하게만 소개하겠다. 이들은 모두 std 이름공간에 속한다.**

* clamp: 주어진 값이 하한보다 작으면 하한을, 상한보다 크면 상한을 돌려준다. 그 외에는 주어진 값을 돌려준다. 비교 함수를 지정할 수 있다. 필요한 헤더는 `<algorithm>`이다.
* for\_each\_n: for\_each와 같되 \[first, last)가 아니라 \[first, first+n)을 입력 범위로 사용하며, first+n을 돌려준다(참고로 for\_each의 직렬 버전은 사용자 지정 함수를 돌려주고 병렬 버전은 아무것도 돌려주지 않는다). 필요한 헤더는 `<algorithm>`이며, 병렬 버전도 있다.
* sample: 주어진 범위의 요소 n개를 주어진 확률 분포에 따라 무작위로 선택한다. 필요한 헤더는 `<algorithm>`이다.
* uninitialized\_move와 uninitialized\_move\_n: 주어진 요소들을 초기화되지 않은 메모리 영역으로 이동한다. 필요한 헤더는 `<memory>`이며, 병렬 버전도 있다.
* reduce: 분산 처리나 병렬 처리 관련 프레임워크 또는 언어에서 흔히 볼 수 있는 Map-Reduce(사상-축약) 패턴의 Reduce 단계에 해당하는 알고리즘이다. 필요한 헤더는 `<numeric>`이며, 병렬 버전도 있다. 참고로 Map에 해당하는 표준 라이브러리 알고리즘은 transform이다. 기존 accumulate 알고리즘에 병렬 버전이 추가되지 않았는데, 대신 reduce의 병렬 버전을 사용하면 된다.
* transform\_reduce: Map-Reduce에 해당하는 알고리즘으로, 요소들을 먼저 변환한 후에 축약한다. 필요한 헤더는 `<numeric>`이며, 병렬 버전도 있다. 기존 inner\_product 알고리즘에 병렬 버전이 추가되지 않았는데, 대신 이 transform\_reduce의 병렬 버전을 사용하면 된다.
* inclusive\_scan과 exclusive\_scan: 요소들의 구간 합(prefix sum; 또는 부분합)들을 구한다. inclusive\_scan은 i번째 요소를 i번째 부분합에 포함하고, exclusive\_scan은 포함하지 않는다. 예를 들어 덧셈과 초기치 0을 사용한다고 할 때 {1, 1, 0, 2, 3}의 inclusive\_scan 결과는 {1, 2, 2, 4, 7}이고 exclusive\_scan 결과는 {0, 1, 2, 2, 4}이다. 덧셈 이외의 합산 함수를 지정할 수 있으며, 부분합의 초기치도 다르게 지정할 수 있다(기본은 0). 필요한 헤더는 `<numeric>`이며, 병렬 버전도 있다. 기존 partial\_sum 알고리즘에 병렬 버전이 추가되지 않았는데, 대신 inclusive\_scan의 병렬 버전을 사용하면 된다.
* transform\_inclusive\_scan과 transform\_inclusive\_scan: 요소들을 먼저 변환한 후 구간 합을 구한다. 필요한 헤더는 `<numeric>`이며, 병렬 버전도 있다.

**그 외에 C++17 표준 라이브러리의 변경 사항을 정리하자면 다음과 같다.**

* map과 unordered\_map에 try\_emplace와 insert\_or\_assign이라는 새로운 멤버 함수가 추가되었다. 이들은 주어진 키가 컨테이너에 없는 경우에만 새 요소를 생성 또는 삽입한다.
* 컨테이너 멤버 함수 size, empty, data의 비멤버 함수 버전인 std::size, std::empty, std::data가 추가되었다( 헤더).
* 메모리를 구성하는 바이트byte의 개념을 좀 더 명시적으로 표현하기 위해 std::byte라는 형식이 추가되었다. 내부적으로 std::byte는 하나의 열거형 클래스(enum class)인데, 바탕 자료 형식은 unsigned char이다. unsigned char와는 달리 std::byte는 문자 형식으로도, 수치(산술) 형식으로도 간주되지 않는다. 개념적으로 std::byte는 단지 비트들의 집합일 뿐이며, 산술 연산자들은 지원하지 않고 비트별 논리 연산자들과 자리이동(shift) 연산자들만 지원한다. 임의의 정수 n을 std::byte 객체로 변환하려면 std::byte{n} 형태의 표현식을 사용하고(C++17부터는 이런 식으로 열거형 객체를 생성할 수 있게 되었다), 그 반대의 변환은 std::to\_integer 함수(역시 C++17에서 추가되었다)를 사용하면 된다.
* 컴파일 시점에서 형식 특질들의 논리합, 논리곱, 부정을 산출하는 메타 함수 conjunction, disjunction, negation이 추가되었으며, is\_aggregate, is\_invocable, is\_swappable 등 다양한 컴파일 시점 형식 판정 함수가 추가되었다(`<type_traits>` 헤더).
* 최대공약수와 최소공배수를 돌려주는 수학 함수 gcd와 lcm이 추가되었으며(`<numeric>` 헤더), 타원적분, 베셀 함수, 르장드르 함수, 노이만 함수, 리만 제타 함수 등 다양한 특수 함수가 추가되었다( 헤더). 표 A.4에 특수 함수들이 나열되어 있다.

### 참고

* <https://medium.com/@snghojeong/c-17-%EC%83%88%EB%A1%9C%EC%9A%B4-%EA%B8%B0%EB%8A%A5%EB%93%A4-558f323c27d1>
* <https://github.com/tvaneerd/cpp17_in_TTs/blob/master/ALL_IN_ONE.md>
