C++

C++17 주요 기능 정리 (1) - 유용한 기능 편

카루-R 2020. 3. 21. 09:17
반응형

글의 모든 예제 코드는 C++17 이상에서만 동작합니다.

(GCC 7-8, MSVC 19.14)

 

Nested namespace definition
중첩 네임스페이스 정의

 만약 여러분이 outer라는 네임스페이스를 만들고 그 안에 inner라는 네임스페이스를 만든다면

namespace outer {
    namespace inner {
        // 어쩌구 저쩌구
    }
}

 일반적으로 이렇게 작성하시겠죠, 하지만 이렇게 두줄이나 낭비할 필요가 있을까요? outer 네임스페이스는 inner 네임스페이스를 제외하면 '빈공간' 입니다.
굳이 중괄호를 두번씩 쓸 필요가 없겠죠.


C++17에는 Nested namespace definition이 있습니다.

namespace outer::inner {
    // 어쩌구 저쩌구
}

훨씬 보기 좋아졌네요.
namespace A::B::C::D::E::F::G도 가능합니다.
(사용자 코드에선 그럼 A::B::C::D::E::F::G::func()...?)

 

Structured Bindings

tuple로 함수가 값을 여러개 반환할 수 있습니다.

tuple<bool, int, string> find(KeyType key) {
    // 어쩌구 저쩌구
    return {false, 3, "String"};
}

C++17의 Deduction Guide 덕분에 return tuple<bool, int, string>{false, 3, "String"}; 이 아니라 위처럼 간략하게 쓸 수 있습니다. 그리고, 그 Deduction Guide와 auto 반환형을 이용하면..

auto find(Keytype key) {
    return tuple{false, 3, "String"};
}

이렇게 줄일 수 있습니다. 자, 일단 함수는 이렇게 간단하게 작성할 수 있습니다. 그런데 함수를 사용하는 입장에선 어떡할까요?

// main함수
auto ret = find(key);
success = get<0>(ret); integer = get<1>(ret); name = get<2>(ret);
......

auto 변수에 받은 뒤, 일일이 get() 함수를 이용해서 값을 풀어야 합니다. 그리고 이 변수들을 미리 선언해야 한다는 단점이 있죠. 일단 지금까지는 tie()를 이용해서 간단히 줄일 수 있었습다.

bool success; int integer; string name;
std::tie(success, integer, name) = find(key);

tie()를 이용하여 lvalue인 변수들을 묶어주어서 대입합니다. 하지만 변수를 미리 선언해야 하고, 레퍼런스를 얻으려면 또 get으로 풀어야 하죠. 걱정 마세요! C++17에는 이런 고민을 날려줄 강력한 한방, Structured Binding이 있습니다!

auto [success, integer, name] = find(key);

상당히 깔끔하죠? 레퍼런스로 받고 싶다면 auto&&로 바꾸면 됩니다. 그렇다면 find의 반환값은 rvalue이므로 각각 bool&&, int&&, string&&이 되겠군요. lvalue라면 bool&, int&, string&이 되겠지만요.

 

init-statements for if and switch
if와 switch의 초기화 구문

가끔씩 정말 짜증날 때가 있습니다.

auto success = testFunc();
if (success == true) {
    // 어쩌구 저쩌구
}

 사실 저것도 if문에 포함되야 하지 않겠습니까? 엄연히 조건식을 위한 초기화입니다.
이걸 꼭 한줄에 구겨넣고 싶은 마음이 있단 말이죠.

auto success = testFunc(); if (success == true)

하지만 이렇게 하면 가독성이 떨어지는 치명적인 단점이 있습니다.
Go언어에서는 코드를 간결하게 하기 위해 이런 문법을 지원합니다.

if err, result := func(); err != nil {
    // 어쩌구 저쩌구
}

 참 마음에 듭니다. 소괄호도 필요없고 (대신 중괄호가 필수이고, if 예약어와 같은 줄에 있어야 합니다) 조건식에 들어갈 변수의 선언을 if문 안에 넣을수가 있으니까요.
func()를 호출하고 에러 확인값과 반환값을 받아 에러가 null인지 (C++의 nullptr = Go의 nil)
확인하는 코드인데, C++에도 이렇게 if안에 초기화 구문을 넣을 수 있다면?
이제 가능합니다!

if (auto success = testFunc(); success == true)

switch문에도 응용이 가능합니다. 앞에서 배운 Structured Binding과 혼합한다면,
방금 Go소스코드와 비슷하게 응용이 가능합니다.

if (auto&& [err, result] = func(); err == true) {
    clam = result; // 어쩌구 저쩌구
}

이런게 가능합니다.

 

constexpr if

아래의 문법도 개인적으로 마음에 들지 않았습니다.

// 멀쩡한, 잘 설계된 합수다
void func() {
    auto x{0}; // C++17부터 int로 추론
#ifdef _32_BIT_ // 갑자기 여기서 전처리기가...
    x = 32;
#elif defined _64_BIT_ 
    x = 64;
#else
    x = 16;
#endif
}

네. 전처리기 블럭들. 저걸 이용하는 이유가 무엇일까요?
전 이렇게 생각합니다.
"거짓인 구간은 아예 컴파일에서 제외되기 때문에."
그렇지만 굳이 매크로를 이용하면서, C++의 문법을 파괴하는게 옳을까요..?
그래서 이를 대체하는 문법이 등장했습다, constexpr if가 바로 그것입니다.

constexpr int bit{32};
void func() {
    auto x{0}; // C++17부터 int로 추론
    if constexpr (bit == 32)
        x = 32;
    else if (bit == 64) // constexpr를 붙일 필요가 없다. 
        x = 64;
    else // 역시 constexpr를 붙일 필요가 없다.
        x = 16;
}

이 역시 거짓인 구간은 아예 컴파일에서 제외됩니다.
endif를 안봐도 되니 개인적으로 정말 속이 시원하군요.

유용한 기능편은 여기서 마치겠습니다.
Fold Expression과 attribute등은 다음편에 이어서 하겠습니다.

반응형