types_common_type

types_common_type (공통 타입 결정)

std::common_type은 여러 타입들이 공통으로 변환될 수 있는 타입을 결정하는 템플릿이에요. 이 페이지에서는 그 동작 방식과 규칙, 그리고 사용자 정의 특수화 방법을 설명할게요. C++11부터 <type_traits> 헤더에서 제공되고 있어요.

출처: cppreference

본문

<type_traits> 헤더에 정의되어 있어요.

정의
template<class... T> struct common_type; (C++11부터)

이 템플릿은 모든 T... 타입들 사이의 공통 타입을 결정해요. 즉, 모든 T...가 명시적으로 변환될 수 있는 타입이에요. 그런 타입이 아래 규칙에 따라 존재한다면, 멤버 타입 type이 그 타입을 가리켜요. 그렇지 않으면 멤버 타입 type은 존재하지 않아요.

  • sizeof...(T)가 0이면, 멤버 타입 type이 없어요.
  • sizeof...(T)가 1이면 (즉 T...가 오직 하나의 타입 T0만 포함), 멤버 타입은 std::common_type<T0, T0>::type이 존재한다면 그와 같은 타입을 가리켜요. 존재하지 않으면 멤버 타입이 없어요.
  • sizeof...(T)가 2이면 (즉 T...가 정확히 두 타입 T1T2를 포함),
    • T1 또는 T2 중 적어도 하나에 std::decay를 적용했을 때 다른 타입이 된다면, 멤버 타입은 std::common_type<std::decay<T1>::type, std::decay<T2>::type>::type이 존재한다면 그와 같은 타입을 가리켜요. 존재하지 않으면 멤버 타입이 없어요.
    • 그렇지 않고, std::common_type<T1, T2>에 대한 사용자 정의 특수화가 있다면 그 특수화가 사용돼요.
    • 그렇지 않고, std::decay<decltype(false ? std::declval<T1>() : std::declval<T2>())>::type이 유효한 타입이라면, 멤버 타입은 그 타입을 가리켜요. 조건 연산자를 참고하세요.
    • (C++20부터) 그렇지 않고, std::decay<decltype(false ? std::declval<CR1>() : std::declval<CR2>())>::type이 유효한 타입이라면 (여기서 CR1CR2는 각각 const std::remove_reference_t<T1>&const std::remove_reference_t<T2>&), 멤버 타입은 그 타입을 가리켜요.
    • 그렇지 않으면 멤버 타입이 없어요.
  • sizeof...(T)가 2보다 크면 (즉 T...가 타입들 T1, T2, R...로 구성), std::common_type<T1, T2>::type이 존재한다면, 멤버 타입은 std::common_type<typename std::common_type<T1, T2>::type, R...>::type이 존재한다면 그 타입을 가리켜요. 그 외 모든 경우에는 멤버 타입이 없어요.

매개변수 팩 T 안의 어떤 타입이 불완전한 타입이거나, (cv 한정된) void이거나, 경계를 알 수 없는 배열이라면, 동작은 정의되지 않아요.

위 템플릿의 인스턴스화가 불완전한 타입에 직간접적으로 의존하고, 그 타입이 가상으로 완성되었을 때 다른 결과를 낳을 수 있다면, 동작은 정의되지 않아요.

멤버 타입

멤버 타입 설명
type 모든 T에 대한 공통 타입

헬퍼 타입

템플릿 설명
template<class... T> using common_type_t = typename common_type<T...>::type; (C++14부터)

특수화

사용자는 다음 조건을 만족하면 T1T2에 대해 common_type을 특수화할 수 있어요.

  • T1T2 중 적어도 하나가 사용자 정의 타입에 의존하고,
  • std::decayT1T2 모두에 대해 항등 변환이어야 해요.

그런 특수화에 type이라는 멤버가 있다면, 그것은 공용이고 모호하지 않은 멤버여야 하며, T1T2 둘 다 명시적으로 변환될 수 있는 cv 한정되지 않은 비참조 타입을 가리켜야 해요. 또한 std::common_type<T1, T2>::typestd::common_type<T2, T1>::type은 같은 타입을 가리켜야 해요.

프로그램이 이러한 규칙을 위반하는 common_type 특수화를 추가하면 동작은 정의되지 않아요.

프로그램이 <type_traits>의 다른 템플릿에 특수화를 추가하면 (C++20부터 std::basic_common_reference 제외) 동작은 정의되지 않아요.

다음 특수화들은 표준 라이브러리에서 이미 제공되고 있어요.

특수화 설명
std::common_type<std::chrono::duration> (C++11) std::common_type 트레이트를 특수화해요 (클래스 템플릿 특수화)
std::common_type<std::chrono::time_point> (C++11) std::common_type 트레이트를 특수화해요 (클래스 템플릿 특수화)
std::common_type<std::pair> (C++23) pair의 공통 타입을 결정해요 (클래스 템플릿 특수화)
std::common_type<tuple-like> (C++23) 튜플과 튜플 유사 타입의 공통 타입을 결정해요 (클래스 템플릿 특수화)
std::common_type<std::basic_const_iterator> (C++23) 반복자와 적응된 basic_const_iterator 타입의 공통 타입을 결정해요 (클래스 템플릿 특수화)

가능한 구현

// primary template (used for zero types)
template<class...>
struct common_type {};

// one type
template<class T>
struct common_type<T> : common_type<T, T> {};

namespace detail {
    template<class...>
    using void_t = void;

    template<class T1, class T2>
    using conditional_result_t = decltype(false ? std::declval<T1>() : std::declval<T2>());

    template<class, class, class = void>
    struct decay_conditional_result {};
    template<class T1, class T2>
    struct decay_conditional_result<T1, T2, void_t<conditional_result_t<T1, T2>>>
        : std::decay<conditional_result_t<T1, T2>> {};

    template<class T1, class T2, class = void>
    struct common_type_2_impl : decay_conditional_result<const T1&, const T2&> {};
    // C++11 implementation:
    // template<class, class, class = void>
    // struct common_type_2_impl {};
    template<class T1, class T2>
    struct common_type_2_impl<T1, T2, void_t<conditional_result_t<T1, T2>>>
        : decay_conditional_result<T1, T2> {};
}

// two types
template<class T1, class T2>
struct common_type<T1, T2>
    : std::conditional<
        std::is_same<T1, typename std::decay<T1>::type>::value &&
        std::is_same<T2, typename std::decay<T2>::type>::value,
        detail::common_type_2_impl<T1, T2>,
        common_type<typename std::decay<T1>::type, typename std::decay<T2>::type>
    >::type {};

// 3+ types
namespace detail {
    template<class AlwaysVoid, class T1, class T2, class... R>
    struct common_type_multi_impl {};
    template<class T1, class T2, class... R>
    struct common_type_multi_impl<void_t<typename common_type<T1, T2>::type>, T1, T2, R...>
        : common_type<typename common_type<T1, T2>::type, R...> {};
}
template<class T1, class T2, class... R>
struct common_type<T1, T2, R...>
    : detail::common_type_multi_impl<void, T1, T2, R...> {};

참고 사항

승격(promotion)되지 않는 산술 타입의 경우, 공통 타입은 T0() + T1() + ... + Tn() 같은 (혼합 모드일 수 있는) 산술 표현식의 타입으로 볼 수 있어요.

예제

프로그램 정의 클래스에서 혼합 모드 산술을 보여줘요.

#include <boost/core/demangle.hpp>
#include <iostream>
#include <type_traits>
#include <typeinfo>

template<class T>
struct Number { T n; };

template<class T, class U>
constexpr Number<std::common_type_t<T, U>>
    operator+(const Number<T>& lhs, const Number<U>& rhs)
{
    return {lhs.n + rhs.n};
}

template<typename T>
std::ostream& operator<< (std::ostream& os, Number<T> x)
{
    return os << boost::core::demangle(typeid(Number<T>).name()) << '{' << x.n << '}';
}

int main()
{
    Number<int> i1{1}, i2{2};
    Number<double> d1{2.3}, d2{3.5};

    std::cout << "i1 + i2 == " << i1 + i2 << "\n"
                 "i1 + d2 == " << i1 + d2 << "\n"
                 "d1 + i2 == " << d1 + i2 << "\n"
                 "d1 + d2 == " << d1 + d2 << "\n";
}

출력:

i1 + i2 == Number<int>{3}
i1 + d2 == Number<double>{4.5}
d1 + i2 == Number<double>{4.3}
d1 + d2 == Number<double>{5.8}

결함 보고서

다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.

DR 적용 대상 발표된 동작 올바른 동작
LWG 2141 C++11 조건 연산자의 결과 타입이 decay되지 않았음 결과 타입을 decay함
LWG 2408 C++11 common_type이 SFINAE 친화적이지 않았음 SFINAE 친화적으로 만듦
LWG 2460 C++11 common_type 특수화를 작성하기가 거의 불가능했음 필요한 특수화 수를 줄임

같이 보기

common_with (C++20) 두 타입이 공통 타입을 공유함을 지정해요 (개념)
common_type (C++26) 반영된 타입들의 시퀀스에 대한 공통 타입을 결정해요 (함수 템플릿)

더 알아보기 (Learn more)

cppreference