if 문
if 문
조건에 따라 다른 문장을 실행하는 if 문을 알아볼게요. 코드를 조건(condition)에 따라 실행하고 싶을 때, 또는 if 문이 명백히 상수 평가되는 문맥(manifestly constant-evaluated context)에서 평가되는지에 따라 분기하고 싶을 때(C++23부터) 쓰는 문장입니다.
출처: cppreference
본문
문법
attr (optional) if constexpr (optional) ( init-statement (optional) condition ) statement-true (1)
attr (optional) if constexpr (optional) ( init-statement (optional) condition ) statement-true else statement-false (2)
attr (optional) if ! (optional) consteval compound-statement (3) (since C++23)
attr (optional) if ! (optional) consteval compound-statement else statement (4) (since C++23)
- (1)
else분기가 없는if문 - (2)
else분기가 있는if문 - (3)
else분기가 없는 consteval if 문 - (4)
else분기가 있는 consteval if 문
| attr | - | (C++11부터) 개수 제한 없는 속성 |
| constexpr | - | (C++17부터) 있으면 그 문장은 constexpr if 문이 됩니다 |
| init-statement | - | (C++17부터) 표현식 문장(널 문장 ;일 수도 있음) 또는 단순 선언. 대개 이니셜라이저가 있는 변수 선언이며, 임의의 많은 변수를 선언하거나 구조적 바인딩 선언일 수도 있어요. 별칭 선언(alias declaration)일 수도 있습니다(C++23부터). 어떤 init-statement든 세미콜론으로 끝나야 한다는 점을 기억하세요. 그래서 흔히 '세미콜론이 뒤따르는 표현식이나 선언'으로 비공식적으로 표현됩니다. |
| condition | - | 조건 |
| statement-true | - | 조건이 true를 산출하면 실행할 문장 |
| statement-false | - | 조건이 false를 산출하면 실행할 문장 |
| compound-statement | - | if 문이 명백히 상수 평가되는 문맥에서 평가되면 실행할 복합문(consteval 앞에 !가 있으면 그런 문맥이 아닐 때 실행) |
| statement | - | if 문이 명백히 상수 평가되는 문맥에서 평가되지 않으면 실행할 문장(복합문이어야 해요, 아래 참고) (!가 consteval 앞에 있으면 그런 문맥에서 평가될 때 실행) |
조건 (Condition)
조건은 표현식이거나 단순 선언(simple declaration)일 수 있어요.
- 문법적으로 구조적 바인딩 선언으로 해석될 수 있으면 구조적 바인딩 선언으로 해석돼요. (C++26부터)
- 문법적으로 표현식으로 해석될 수 있으면 표현식으로 처리돼요. 그렇지 않으면 구조적 바인딩 선언이 아닌 선언으로 처리됩니다. (C++26부터)
제어가 조건에 도달하면 조건은 값을 산출하며, 이 값으로 제어가 어느 분기로 갈지가 결정돼요.
표현식 조건
조건이 표현식이면, 산출되는 값은 그 표현식의 값을 문맥적으로 bool로 변환한 값이에요. 그 변환이 ill-formed이면 프로그램도 ill-formed이 됩니다.
선언 조건
조건이 단순 선언이면, 산출되는 값은 결정 변수(decision variable, 아래 참고)의 값을 문맥적으로 bool로 변환한 값이에요. 그 변환이 ill-formed이면 프로그램도 ill-formed이 됩니다.
구조적 바인딩이 아닌 선언
선언에는 다음 제약이 있어요:
- 문법적으로 다음 형태를 따릅니다:
type-specifier-seq declarator = assignment-expression(C++11 이전),attribute-specifier-seq (optional) decl-specifier-seq declarator brace-or-equal-initializer(C++11부터) - declarator는 함수나 배열을 지정할 수 없어요.
- 타입 지정자 시퀀스(C++11 이전)는 타입 지정자와
constexpr만 포함할 수 있으며, 선언 지정자 시퀀스(C++11부터)는 클래스나 열거형을 정의할 수 없어요.
선언의 결정 변수는 선언된 변수예요. (C++26부터) 구조적 바인딩 선언의 경우 이니셜라이저의 표현식이 배열 타입일 수 없고, 결정 변수는 선언이 도입하는 가상 변수(invented variable) e예요.
분기 선택 (Branch selection)
조건이 true를 산출하면 statement-true가 실행돼요. if 문에 else 부분이 있고 조건이 false를 산출하면 statement-false가 실행됩니다.
if 문에 else 부분이 있고 statement-true도 if 문이라면, 그 내부 if 문도 반드시 else 부분을 가져야 해요(다시 말해 중첩 if 문에서 else는 아직 연관된 else가 없는 가장 가까운 if에 결합됩니다).
#include <iostream>
int main()
{
// simple if-statement with an else clause
int i = 2;
if (i > 2)
std::cout << i << " is greater than 2\n";
else
std::cout << i << " is not greater than 2\n";
// nested if-statement
int j = 1;
if (i > 1)
if (j > 2)
std::cout << i << " > 1 and " << j << " > 2\n";
else // this else is part of if (j > 2), not of if (i > 1)
std::cout << i << " > 1 and " << j << " <= 2\n";
// declarations can be used as conditions with dynamic_cast
struct Base
{
virtual ~Base() {}
};
struct Derived : Base
{
void df() { std::cout << "df()\n"; }
};
Base* bp1 = new Base;
Base* bp2 = new Derived;
if (Derived* p = dynamic_cast<Derived*>(bp1)) // cast fails, returns nullptr
p->df(); // not executed
if (auto p = dynamic_cast<Derived*>(bp2)) // cast succeeds
p->df(); // executed
}
출력:
2 is not greater than 2
2 > 1 and 1 <= 2
df()
이니셜라이저가 있는 if 문
init-statement를 쓰면 if 문은 다음과 동등해져요:
{ init-statement attr (optional) if constexpr (optional) ( condition ) statement-true }
{ init-statement attr (optional) if constexpr (optional) ( condition ) statement-true else statement-false }
단, init-statement가 선언이라면 그 선언된 이름과, condition이 선언이라면 그 선언된 이름이 같은 스코프에 있어요. 그 스코프는 두 문장 모두의 스코프이기도 합니다. (C++17부터)
std::map<int, std::string> m;
std::mutex mx;
extern bool shared_flag; // guarded by mx
int demo()
{
if (auto it = m.find(10); it != m.end())
return it->second.size();
if (char buf[10]; std::fgets(buf, 10, stdin))
m[0] += buf;
if (std::lock_guard lock(mx); shared_flag)
{
unsafe_ping();
shared_flag = false;
}
if (int s; int count = ReadBytesWithSignal(&s))
{
publish(count);
raise(s);
}
if (const auto keywords = {"if", "for", "while"};
std::ranges::any_of(keywords, [&tok](const char* kw) { return tok == kw; }))
{
std::cerr << "Token must not be a keyword\n";
}
}
constexpr if
if constexpr로 시작하는 문장을 constexpr if 문이라고 해요. constexpr if 문의 모든 하위 문장은 제어 흐름이 제한된 문장(control-flow-limited statement)이에요.
constexpr if 문에서 condition은 bool 타입으로 문맥적으로 변환된 상수 표현식이어야 하며(C++23 이전), 문맥적으로 bool로 변환된 표현식이어야 하고 그 변환이 상수 표현식이어야 해요(C++23부터).
조건이 true를 산출하면 statement-false가(있다면) 폐기(discard)되고, 그렇지 않으면 statement-true가 폐기돼요. 폐기된 문장 안의 return 문은 함수 반환 타입 추론에 참여하지 않습니다:
template<typename T>
auto get_value(T t)
{
if constexpr (std::is_pointer_v<T>)
return *t; // deduces return type to int for T = int*
else
return t; // deduces return type to int for T = int
}
폐기된 문장은 정의되지 않은 변수를 ODR-사용할 수 있어요:
extern int x; // no definition of x required
int f()
{
if constexpr (true)
return 0;
else if (x)
return x;
else
return -x;
}
템플릿 밖에서는 폐기된 문장도 완전히 검사됩니다. if constexpr은 #if 전처리 지시문의 대체물이 아니에요:
void f()
{
if constexpr (false)
{
int i = 0;
int* p = i; // Error even though in discarded statement
}
}
constexpr if 문이 템플릿화된 엔터티 안에 나타나고, 인스턴스화 후에 condition이 값에 의존(value-dependent)하지 않으면, 폐기된 문장은 바깥 템플릿이 인스턴스화될 때 인스턴스화되지 않아요:
template<typename T, typename... Rest>
void g(T&& p, Rest&&... rs)
{
// ... handle p
if constexpr (sizeof...(rs) > 0)
g(rs...); // never instantiated with an empty argument list
}
인스턴스화 후에도 값에 의존해 있는 조건은 중첩 템플릿이에요:
template<class T>
void g()
{
auto lm = [=](auto p)
{
if constexpr (sizeof(T) == 1 && sizeof p == 1)
{
// this condition remains value-dependent after instantiation of g<T>,
// which affects implicit lambda captures
// this compound statement may be discarded only after
// instantiation of the lambda body
}
};
}
폐기된 문장은 모든 가능한 특수화에 대해 ill-formed일 수 없어요:
template<typename T>
void f()
{
if constexpr (std::is_arithmetic_v<T>)
// ...
else
{
using invalid_array = int[-1]; // ill-formed: invalid for every T
static_assert(false, "Must be arithmetic"); // ill-formed before CWG2518
}
}
CWG 2518 이슈가 구현되기 전에 이런 catch-all 문장에서 쓰던 일반적인 우회책은, 항상 false인 타입 의존 표현식이었어요:
template<typename>
constexpr bool dependent_false_v = false;
template<typename T>
void f()
{
if constexpr (std::is_arithmetic_v<T>)
// ...
else
{
// workaround before CWG2518
static_assert(dependent_false_v<T>, "Must be arithmetic");
}
}
typedef 선언이나 별칭 선언(C++23부터)을 constexpr if 문의 init-statement로 사용해서 타입 별칭의 스코프를 좁힐 수 있어요.
consteval if
if consteval로 시작하는 문장을 consteval if 문이라고 해요. consteval if 문의 모든 하위 문장은 제어 흐름이 제한된 문장이에요. statement는 복합문이어야 하며, 복합문이 아니어도 여전히 consteval if 문의 일부로 취급돼요(그래서 컴파일 오류가 납니다):
constexpr void f(bool b)
{
if (true)
if consteval {} else ; // error: not a compound-statement
// else not associated with outer if
}
consteval if 문이 명백히 상수 평가되는 문맥에서 평가되면 compound-statement가 실행돼요. 그렇지 않으면, statement가 있다면 그것이 실행됩니다.
문장이 if ! consteval로 시작하면 compound-statement와 statement(있다면)가 모두 복합문이어야 해요. 그런 문장은 consteval if 문으로 간주되지 않지만, consteval if 문과 동등해요:
if ! consteval { /* stmt */ }는if consteval {} else { /* stmt */ }와 동등if ! consteval { /* stmt-1 */ } else { /* stmt-2 */ }는if consteval { /* stmt-2 */ } else { /* stmt-1 */ }와 동등
consteval if 문의 compound-statement(부정 형태에서는 statement)는 immediate function 문맥 안에 있는데, 그 문맥에서는 immediate 함수 호출이 상수 표현식일 필요가 없어요.
#include <cmath>
#include <cstdint>
#include <cstring>
#include <iostream>
constexpr bool is_constant_evaluated() noexcept
{
if consteval { return true; }
else { return false; }
}
constexpr bool is_runtime_evaluated() noexcept
{
if not consteval { return true; }
else { return false; }
}
consteval std::uint64_t ipow_ct(std::uint64_t base, std::uint8_t exp)
{
if (!base) return base;
std::uint64_t res{1};
while (exp)
{
if (exp & 1) res *= base;
exp /= 2;
base *= base;
}
return res;
}
constexpr std::uint64_t ipow(std::uint64_t base, std::uint8_t exp)
{
if consteval // use a compile-time friendly algorithm
{
return ipow_ct(base, exp);
}
else // use runtime evaluation
{
return std::pow(base, exp);
}
}
int main(int, const char* argv[])
{
static_assert(ipow(0, 10) == 0 && ipow(2, 10) == 1024);
std::cout << ipow(std::strlen(argv[0]), 3) << '\n';
}
참고 사항
statement-true나 statement-false가 복합문이 아니면 복합문인 것처럼 처리돼요:
if (x)
int i;
// i is no longer in scope
는 다음과 같습니다:
if (x)
{
int i;
}
// i is no longer in scope
condition이 선언이라면 그 선언이 도입한 이름의 스코프는 두 문장 본문의 스코프를 합친 것입니다:
if (int x = f())
{
int x; // error: redeclaration of x
}
else
{
int x; // error: redeclaration of x
}
statement-true가 goto나 longjmp로 진입되면, condition은 평가되지 않고 statement-false는 실행되지 않아요.
constexpr if 문의 조건에서는 비좁히지 않는(non-narrowing) 정수 변환을 bool로 하는 경우를 제외하고 내장 변환(built-in conversion)이 허용되지 않아요. (C++17부터 C++23까지)
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
| __cpp_if_constexpr | 201606L | (C++17) | constexpr if |
| __cpp_if_consteval | 202106L | (C++23) | consteval if |
키워드
if, else, constexpr, consteval
더 알아보기 (Learn more)
- is_constant_evaluated (C++20) — 호출이 상수 평가 문맥에서 일어나는지 감지하는 함수
- C documentation for if statement — C 언어에서의
if - 조건 (condition) — 조건으로 쓸 수 있는 형태