아래는 **“이번 주의 팁 #198: 태그 타입(Tag Types)”**에 대한 한글 번역입니다.


제목: “이번 주의 팁 #198: 태그 타입(Tag Types)”

원문 게시일: 2021년 8월 12일
업데이트: 2022년 1월 24일

작성자: Alex Konradi

빠른 링크: abseil.io/tips/198


개요

다음과 같은 Foo 클래스를 고려해 봅시다:

CPP
class Foo {
 public:
  explicit Foo(int x, int y);
  Foo& operator=(const Foo&) = delete;
  Foo(const Foo&) = delete;
};
클릭하여 더 보기

Foo이동이나 복사가 불가능하지만, 생성자는 공개되어 있으므로 다음과 같이 인스턴스를 만들 수 있습니다:

CPP
std::optional<Foo> maybe_foo;
maybe_foo.emplace(5, 10);
클릭하여 더 보기

하지만 std::optionalconst로 선언되면 emplace()를 호출할 수 없습니다. 다행히도 std::optional은 이를 해결할 수 있는 생성자를 제공합니다:

CPP
const std::optional<Foo> maybe_foo(std::in_place, 5, 10);
클릭하여 더 보기

여기서 **std::in_place**는 무엇일까요? 문서를 살펴보면 std::optional 생성자 중 하나는 std::in_place_t 타입을 첫 번째 인자로 받습니다. **std::in_place**는 std::in_place_t의 인스턴스이므로 컴파일러는 이 인자를 보고 적절한 **“emplacing 생성자”**를 선택합니다.


태그 타입(Tag Types)을 활용한 오버로드 해소

**std::in_place_t**는 **태그 타입(tag type)**의 일종입니다. 태그 타입은 특정 오버로드를 선택하기 위해 컴파일러에게 정보를 제공하는 역할을 합니다. 이러한 태그 타입의 인스턴스를 함수나 생성자의 첫 번째 인자로 전달하면, 컴파일러는 해당 타입과 일치하는 오버로드를 선택합니다.

예를 들어 std::optional의 경우, 첫 번째 인자가 std::in_place_t이므로 컴파일러는 **“생성자 오버로드”**를 통해 Foo의 생성자를 호출합니다.

이 개념은 C++11의 **std::piecewise_construct_t**에서 시작되어 C++17부터는 **std::in_place_t**와 같은 여러 태그 타입이 추가되었습니다.


템플릿과 태그 타입

태그 타입은 오버로드 해소 외에도 템플릿 생성자에 타입 정보를 전달하는 데 유용합니다. 다음과 같은 두 구조체를 예로 들어보겠습니다:

CPP
struct A { A(); /* 내부 멤버 */ };
struct B { B(); /* 내부 멤버 */ };
클릭하여 더 보기

std::variant<A, B>를 사용해 A 또는 B를 생성하려면 다음과 같이 시도할 수 있습니다:

CPP
// A와 B가 복사 또는 이동 생성 가능할 경우 작동하지만, 불필요한 복사 비용이 발생합니다.
std::variant<A, B> with_a{A()};
std::variant<A, B> with_b{B()};

// C++는 생성자에 명시적으로 템플릿 매개변수를 지정하는 문법을 지원하지 않습니다.
std::variant<A, B> try_templating_a<A>{};
std::variant<A, B><B> try_templating_b{};
클릭하여 더 보기

이 문제를 해결하기 위해 **std::in_place_type**을 사용할 수 있습니다:

CPP
std::variant<A, B> with_a{std::in_place_type<A>};
std::variant<A, B> with_b{std::in_place_type<B>};
클릭하여 더 보기

**std::in_place_type<T>**는 std::in_place_type_t<T>의 인스턴스로, 이 태그를 사용하면 컴파일러는 AB를 생성할 타입으로 선택합니다.


태그 타입 사용법

태그 타입은 주로 표준 라이브러리의 제네릭 클래스 템플릿과 상호작용할 때 등장합니다. 하지만 읽기 쉬운 코드 작성을 위해 팩토리 함수를 사용할 수 있습니다. 예를 들어:

CPP
// 태그 타입을 사용한 코드 (의도를 파악하기 어려움)
std::optional<Foo> with_tag(std::in_place, 5, 10);

// 팩토리 함수를 사용한 코드 (의도가 더 명확함)
std::optional<Foo> with_factory = std::make_optional<Foo>(5, 10);
클릭하여 더 보기

팩토리 함수는 태그 타입보다 가독성이 높지만, 특정 상황에서는 동작하지 않을 수 있습니다. 예를 들어:

CPP
// Foo가 move-constructible이 아닌 경우 컴파일 오류가 발생합니다.
std::optional<std::optional<Foo>> foo(std::make_optional<Foo>(5, 10));
클릭하여 더 보기

이 문제는 **std::in_place**를 사용하면 해결됩니다:

CPP
// 모든 것을 제자리에서 생성하여 Foo의 생성자를 단 한 번만 호출합니다.
std::optional<std::optional<Foo>> foo(std::in_place, std::in_place, 5, 10);
클릭하여 더 보기

태그 타입의 장점


결론

태그 타입은 컴파일러에게 추가 정보를 제공하고, 오버로드를 해소하는 강력한 방법입니다. 표준 라이브러리에서는 생성자 호출 시 태그 타입을 사용해 명확성유연성을 확보합니다. 여러분도 필요에 따라 태그 타입을 사용하여 오버로드나 템플릿 타입 전달을 해결할 수 있습니다.


추가 참고 자료

라이선스

저작자: Jaehun Ryu

링크: https://jaehun.me/posts/abseil-tip-198-%ED%83%9C%EA%B7%B8-%ED%83%80%EC%9E%85tag-types/

라이선스: CC BY 4.0

이 저작물은 크리에이티브 커먼즈 저작자표시 4.0 국제 라이선스에 따라 이용할 수 있습니다. 출처를 밝히면 상업적 목적을 포함해 자유롭게 이용 가능합니다.

댓글

검색 시작

검색어를 입력하세요

↑↓
ESC
⌘K 단축키