변환 연산자(Conversion operator, cast operator)
변환 연산자(Conversion operator, cast operator)
어떤 클래스 타입의 객체를 다른 타입으로 자연스럽게 바꿔 주고 싶을 때가 있어요. 그때 클래스 안에 정의하는 것이 **변환 연산자(conversion function, cast operator)**예요. 클래스 타입에서 다른 타입으로의 암시적 변환이나 명시적 변환을 가능하게 해 주는 멤버 함수예요.
출처: cppreference
본문
문법
변환 함수는 매개변수도, 명시적 반환 타입도 없고 다음 형태의 이름을 가진 비정적 멤버 함수(또는 멤버 함수 템플릿)로 선언해요.
operator 변환-타입-id |
(1) | |
explicit operator 변환-타입-id |
(2) | (C++11부터) |
explicit ( 표현식 ) operator 변환-타입-id |
(3) | (C++20부터) |
**변환-타입-id(conversion-type-id)**는 type-id인데, 단 그 선언자에는 함수나 배열 선언자 [] 또는 ()가 허용되지 않아요(그래서 배열에 대한 포인터 같은 타입으로의 변환은 타입 별칭/typedef나 항등 템플릿이 필요해요. 아래 참고). typedef를 쓰더라도 변환-타입-id가 배열이나 함수 타입을 나타낼 수는 없어요. 이 함수의 반환 타입은 변환-타입-id가 나타내요.
선언에 명시적 반환 타입은 허용되지 않지만, 선언 문법의 decl-specifier-seq은 올 수 있고 type-specifier나 키워드 static을 제외한 어떤 지정자도 포함할 수 있어요. 특히 explicit 외에도 inline, virtual, constexpr(C++11부터), consteval(C++20부터), friend 지정자가 허용돼요(friend는 정규화된 이름을 요구해요: friend A::operator B();처럼).
클래스 X 안에 이런 멤버 함수를 선언하면, X에서 변환-타입-id로의 변환을 수행해요.
struct X
{
// 암시적 변환
operator int() const { return 7; }
// 명시적 변환
explicit operator int*() const { return nullptr; }
// Error: 배열 연산자는 변환-타입-id에 허용되지 않음
// operator int(*)[3]() const { return nullptr; }
using arr_t = int[3];
operator arr_t*() const { return nullptr; } // typedef를 통하면 OK
// operator arr_t () const; // Error: 어떤 경우에도 배열로 변환은 허용되지 않음
};
int main()
{
X x;
int n = static_cast<int>(x); // OK: n을 7로 설정
int m = x; // OK: m을 7로 설정
int* p = static_cast<int*>(x); // OK: p를 null로 설정
// int* q = x; // Error: 암시적 변환 없음
int (*pa)[3] = x; // OK
}
동작 방식(Explanation)
사용자 정의 변환 함수는 암시적 변환의 두 번째 단계에서 호출돼요. 이 단계는 변환 생성자 0개 또는 1개, 사용자 정의 변환 함수 0개 또는 1개로 구성돼요.
어떤 사용자 정의 변환을 수행하는 데 변환 함수와 변환 생성자 모두 사용될 수 있다면, 복사 초기화(copy-initialization)와 참조 초기화(reference-initialization) 문맥에서는 오버로드 결정이 변환 함수와 생성자를 둘 다 고려하지만, 직접 초기화(direct-initialization) 문맥에서는 생성자만 고려돼요.
struct To
{
To() = default;
To(const struct From&) {} // 변환 생성자
};
struct From
{
operator To() const {return To();} // 변환 함수
};
int main()
{
From f;
To t1(f); // 직접 초기화: 생성자 호출
// 참고: 변환 생성자를 사용할 수 없으면 암시적 복사 생성자가 선택되고,
// 그 인자를 준비하기 위해 변환 함수가 호출돼요
// To t2 = f; // 복사 초기화: 모호(ambiguous)
// 참고: 변환 함수가 non-const 타입에서 온 경우, 예를 들어
// From::operator To(); 라면 이 경우 생성자 대신 그것이 선택돼요
To t3 = static_cast<To>(f); // 직접 초기화: 생성자 호출
const To& r = f; // 참조 초기화: 모호
}
자기 자신의 (cv-한정될 수 있는) 클래스로(또는 그 참조로), 자기 클래스의 기반으로(또는 그 참조로), 그리고 void 타입으로의 변환 함수를 정의할 수는 있지만, 일부 경우 가상 디스패치를 통해서가 아니라면 변환 시퀀스의 일부로 실행될 수는 없어요.
struct D;
struct B
{
virtual operator D() = 0;
};
struct D : B
{
operator D() override { return D(); }
};
int main()
{
D obj;
D obj2 = obj; // D::operator D()를 호출하지 않음
B& br = obj;
D obj3 = br; // 가상 디스패치를 통해 D::operator D() 호출
}
변환 함수는 멤버 함수 호출 문법으로도 호출할 수 있어요.
struct B {};
struct X : B
{
operator B&() { return *this; };
};
int main()
{
X x;
B& b1 = x; // X::operator B&()를 호출하지 않음
B& b2 = static_cast<B&>(x); // X::operator B&()를 호출하지 않음
B& b3 = x.operator B&(); // X::operator B&() 호출
}
변환-타입-id는 **탐욕적(greedy)**이에요. 즉, 변환-타입-id를 이룰 수 있는 (있을 경우 속성까지 포함한) 가장 긴 토큰 시퀀스를 의미해요. (C++11부터)
& x.operator int * a; // error: & (x.operator int*) a 로 해석됨
// & (x.operator int) * a 가 아님
operator int [[noreturn]] (); // error: noreturn 속성이 타입에 적용됨
변환-타입-id에 자리표시자 auto를 쓸 수 있어서, 추론된 반환 타입을 나타낼 수 있어요. (C++14부터)
struct X
{
operator int(); // OK
operator auto() -> short; // error: 후행 반환 타입은 문법의 일부가 아님
operator auto() const { return 10; } // OK: 추론된 반환 타입
operator decltype(auto)() const { return 10l; } // OK: 추론된 반환 타입
};
참고: 변환 함수 템플릿은 추론된 반환 타입을 가질 수 없어요.
변환 함수는 상속될 수 있고 가상(virtual)일 수도 있지만, static일 수는 없어요. 파생 클래스의 변환 함수는 같은 타입으로 변환하는 경우가 아니면 기반 클래스의 변환 함수를 숨기지 않아요.
변환 함수는 템플릿 멤버 함수일 수 있어요. 예를 들어 std::auto_ptr<T>::operator auto_ptr<Y> 같은 경우예요. 적용되는 특별 규칙은 멤버 템플릿(member template)과 템플릿 인자 추론(template argument deduction)에서 다뤄요.
결함 보고(Defect reports)
이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 296 | C++98 | 변환 함수가 static일 수 있었음 | static으로 선언할 수 없음 |
| CWG 2016 | C++98 | 변환 함수가 반환 타입을 지정할 수 없었지만, 변환-타입-id에는 타입이 존재함 | 변환 함수의 선언 지정자에 반환 타입을 지정할 수 없음 |
| CWG 2175 | C++11 | operator int [[noreturn]] ();에서 [[noreturn]]이 noptr-declarator(함수 선언자)의 일부로 해석되는지 변환-타입-id의 일부로 해석되는지가 불명확했음 |
변환-타입-id의 일부로 해석됨 |
더 알아보기
operator: 연산자 오버로딩 전반.- 멤버 템플릿과 템플릿 인자 추론: 변환 함수 템플릿에 적용되는 특별 규칙.