이 글의 모든 예제 코드는 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등은 다음편에 이어서 하겠습니다.
'C++' 카테고리의 다른 글
나만의 C++ 프로그래밍 가이드 (0) | 2022.10.01 |
---|---|
[C++17] 템플릿 인자를 생략한다? Deduction Guide (0) | 2020.03.21 |
[C++11] 템플릿으로 배열을 함수에 넘기기 (0) | 2020.03.21 |
[C++20] printf() 쓰지 마세요! std::format() (2) | 2020.03.21 |
[C++11] std::array vs. C-array (0) | 2020.03.21 |