[The C++ Programming Language에서 발췌]


    C++는 표현식으로부터 타입을 추론하기 위해 두 가지의 매커니즘을 제공한다.

    ● 초기화 식으로부터 객체의 타입을 추론하기 위한 auto로, 타입은 변수, const, constexpr의 타입이 될 수 있디.

    ● 함수의 반환 타입이나 클래스 멤버의 타입 같이 간단한 초기화 식이 아닌 뭔가의 타입을 추론하기 위한 decltype(expr)이다.

    여기서 수행되는 추론은 매우 간단한다. 즉, auto와 decltype()은 컴파일러가 이미 알고 있는 표현식의 타입을 알려주기만 할 뿐이다.


    1.1 auto 타입 지정자

    변수의 선먼문에 초기화 식이 있을 때는 타입을 명시적으로 지정할 필요가 없다. 대신 변수가 자신의 초기화 식에 해당하는 타입을 갖게 할 수 있다. 다음을 살펴보자.


    1
    2
    3
    int  a1 = 123;
    char a2 = 123;
    auto a3 = 123; // 3의 타입은 "int"
    cs


    정수 리터럴 123의 타입은 int이므로 a3는 int다. 즉, auto는 초기화 식 타입의 보관 장소다. 123처럼 간단한 표현식에서 int 대신에 auto를 사용하는 건 큰 도움이 되지 않는다. 타입이 쓰기 어렵고 파악하기 어려울수록 auto가 좀 더 빛을 발한다. 예를 들어 다음과 같다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename T>
    void f1(vector<T>& arg)
    {
        for (typename vector<T>::iterator p = arg.bggin(); p != arg.end(); p++)
            *= 7;
     
        for (auto p = arg.begin(); [ != arg.end(); p++)
            *= 7;
    }
     
    cs


    auto를 사용하는 루프가 작성하기도 좀 더 편하고 읽기에도 좀 더 편하다. 또한 코드 변경에도 좀 더 유연하게 대응할 수 있다. 이를테면 arg를 list로 바꿔도 auto를 사용하는 루프는 정확히 동작하는 반면, 첫 번째 루프는 재작성이 필요할 것이다. 그러므로 특별한 이유가 있지 않는 한 auto는 작은 유효 범위로 사용하기 바란다.


    유효 범위가 지나치게 넓으면 타입을 명시적으로 언급함으로써 오류의 영향을 줄일 수 있다. 즉, 구체적인 타입을 사용하는 방식과 비교할 때 auto를 사용하면 타입 오류의 탐지를 지연시킬 수 있다. 예를 들면 다음과 같다.


    1
    2
    3
    4
    5
    6
    7
    8
    void f(complex<double> d)
    {
        // ...
        auto   max = d + 7;
        double min = d - 9;
        // ...
    }
     
    cs


    auto가 문제를 일으키면 최선의 태처법은 대개 함수를 좀 더 작게 만드는 것인데, 이 방식은 어쨋든 대부분 의 경우 좋은 방법이다.

    추론된 타입은 const나 & 같은 지정자나 수정자로 장식할 수 있다. 예를 들면 다음과 같다.


    1
    2
    3
    4
    5
    6
    7
    void f(vector<int>& v)
    {
        for (const auto& x : v) // x는 const int&
        {
            // ...
        }
    }
    cs


    여기서 auto는 v의 원소 타입으로 결정되는데, 즉 int다.

    참조는 표현식에서 암시적으로 역참조되기 때문에 표현식의 타입은 절대로 참조가 될 수 없다는 점에 유의하다. 


    1
    2
    3
    4
    5
    void g(int& v)
    {
        auto  x = v; // x는 int
        auto& y = v; // y는 int&
    cs



    1.2 auto와 {} 리스트

    초기화하는 객체의 타입을 명시적으로 지정하는 경우 두 개의 타입을 고려해야 하는데, 객체의 타입과 칙화 식의 타입이 그것이다. 다음 예를 살펴보자.


    1
    2
    3
    char v1 = 12345// 12345는 int
    int  v2 = 'c';   // 'c'는 char
    T v3 = f();
    cs


    그러한 정의에 {} 초기화 식 문법을 사용함으로써 우려스러운 변환의 가능성을 최소화한다.


    1
    2
    3
    char v1 { 12345 }; // 오류 : 축소
    int  v2 { 'c' };   // 문제없음 : 암시적 char->int 변환
    T    v3 { f() };   // f()의 타입이 묵시적으로 T로 변환 가능할 때만 동작
    cs


    auto를 사용할 때는 오직 하나의 타입, 즉 초기화 식의 타입만이 연관되며, = 문법을 안전하게 사용할 수 있다.


    1
    2
    3
    auto v1 = 12345// v1은 int
    auto v2 = 'c';   // v2는 char
    auto v3 = f();   // v3는 적절한 어떤 타입
    cs


    실제로는 auto를 =와 함께 사용하는 방식이 유리할 수 있는데, {} 리스트 문법이 누군가에는 낯설 수 있기 때문이다.


    1
    2
    3
    auto v1 { 12345 }; // v1은 int의 리스트
    auto v2 { 'c' };   // v2는 char의 리스트
    auto v3 { f() };   // v3는 적절한 어떤 타입의 리스트
    cs


    여기에는 논리적 문제가 없다. 다음을 살펴보자.


    1
    2
    3
    4
    auto x0 {};          // 오류 : 타입을 추론할 수 없다.
    auto x1 { 1 };       // 한 개의 원소를 갖는 int의 리스트
    auto x2 { 12 };    // 두 개의 원소를 갖는 int의 리스트
    auto x3 { 123 }; // 세 개의 원소를 갖는 int의 리스트
    cs


    동일한 종류의 T 타입 원소로 이뤄진 리스트의 타입이 Initializer_list<T> 타입인 것으로 받아들여진다. 특히 x1의 타입은 int로 추론되지 않는다. 특히 x1의 타입은 int로 추론되지 않는다. 그랬었다면 x2와 x3의 타입은 무엇으로 됐겠는가?

    결론적으로 '리스트'로 사용하지 않을 것이라면 auto 지정된 객체에는 {}보다 =를 사용하기를 권장한다.



    2. decltype() 지정자

    적합한 초기화 식이 있다면 auto를 사용할 수 있다. 하지만 경우에 따라 초기화된 변수를 정의하지 않고 타입이 추론되기를 원할 수도 있다. 그러한 경우에 선언 타입 지정자인 decltype(expr)을 사용할 수 있는데, 이 지정자는 expr의 선언된 타입이다. 이 지정자는 일반화 프로그래밍에서 가장 유용하다. 서로 다른 원소 타입을 가질 가능성이 높은 두 개의 행렬을 더하는 함수를 작성한다고 가정해보자. 덧셈의 결과 타입은 무엇이 돼야 하겠는가? 당연히 결과는 행렬이 되겠지만, 그 행렬의 타입은 무엇이 돼야 할까? 명백히 답은 덧셈의 원소 타입은 원소를 더한 것의 타입이라는 것이다. 따라서 다음과 같이 선언할 수 있다.


    1
    2
    template <typename T, typename U>
    auto operator+(const Matrix<T>& a, const Mateix<U>& b) -> Matrix<decltype(T{} + U{})>;
    cs


    여기서는 Matrix<decltype(T{} + U{})>와 같이 인자라는 관점에서 반환 타입을 표현하기위해 접미사 반환 타입 문법을 활용한다. 즉, 결과는 원소 타입을 가진 Matrix로서 인자 Matrixes(T{} + U{})에 포함된 두 개의 원소를 더한 결과에서 얻어지는 것이다.

    정의에서 Matrix의 원소 타입을 표현하기 위해 또 한 번 decltype()이 필요하다.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename T, typename U>
    auto operator+(const Matrix<T>& a, const Matrix<U>& b) -> Matrix<decltype(T{} + U{})>
    {
        Matrix<decltype(T{} + U{}> res;
        for (int i = 0; i < != a.Rows(); i++)
            for (int j = 0; j != a.Cols(); j++)
                    res(i, j) += (a(i, j) + b(i,j));
        return;
    }
    cs


    [C++14부턴 반환형으로 뒤에 decltype을 쓸 필요 없이 auto만 써줘도 됨.]

     


    Posted by Muramasa