본문 바로가기
컴퓨터/Modern Effective C++ 정리

Modern Effective C++ Item 2 : auto 에서의 타입추론

by 김짱쌤 2015. 3. 3.

Item 2 : auto 의 타입 추론

auto의 타입 추론은 template의 타입 추론의 다른 형태

  • auto 는 내부적(개념적)으로 템플릿 함수를 호출하여 T의 결과를 auto로 추론한다.
    • auto x = 27;
    • 위의 오토 선언은 아래의 템플릿함수와 함수 호출로 대체될 수 있다.
    template<typename T>
    void func_for_x(T param); 
    //auto는 T로 변화하고 void(ParamType) 형의 함수가 만들어 진다.
    func_for_x(27); 
    //이 함수를 호출했을때 템플릿 함수의 타입추론 과정을 그대로 따라가면,
    //param이 int가 되고 T가 int로 매칭된다. 따라서 auto 는 int임을 추론할 수 있다.

Item 1 의 모든 내용은 auto에서 똑같이 적용됨

  • case 1 : 형식 지정자가 포인터 / 레퍼런스 인경우 (전역 레퍼런스 제외)

    const auto& rx = x;
    
    template<typename T>
    void func_for_rx(const T& param); 
    //auto가 T로 
    //auto 포함된 paramType이 그대로 T로 매칭되서 param의 타입으로
    
    func_for_rx(x);
    //0호출시 T가 int가 되므로 auto 는 intrx의 타입은 const int&
  • case 2 : 형식 지정자가 전역 레퍼런스 인 경우.

    • 대입되는 매개변수가 lvalue인 경우
    auto&& urefLvalue = x;
    
    template<typename T>
    void func_for_urefLvalue(T&& param);
    
    func_for_urefLvalue(x);
    //호출시 T가 int&/ ParamType이 int& , auto가 int&로 변환되지만 
    //ParamType이 결과적으로 int&이므로 urefLvalue의 타입은 int&
    • 대입되는 매개변수가 rvalue인 경우
    auto&& urefRvalue = 27;
    
    template<typename T>
    void func_for_urefLvalue(T&& param);
    
    func_for_urefLvalue(27);
    //호출시 T는 int / ParamType은 int&&, 
    //auto가 int, urefRvalue의 타입은 int&&
  • case 3 : 형식 지정자가 포인터 / 레퍼런스 둘 다 아닌 경우.

    auto x = 27; //위에서 이미 언급
  • 배열의 경우 (함수의 경우와 동일)

    const char name[] = "Kim Ozt";
    • case 1

      auto arr1 = name;  
      
      template<typename T>
      void func_for_arr1(T param);
      
      func_for_arr1(name);
      //배열은 매개변수로 못받아서 *로 decay
      //T는 const char*로 / ParamType도 const char*
    • case 2

      auto& arr2 = name;
      
      template<typename T>
      void func_for_arr1(T& param);
      
      func_for_arr1(name);
      //배열의 레퍼런스는 매개변수로 OK
      //T는 const char(&)[]로 / ParamType도 const char(&)[]

auto 의 타입 추론과 template의 타입추론의 차이점.

  • 결론 : brace( {...} )를 포함한 초기화에서 auto와 template이 차이가 발생
  • brace를 포함한 초기화 ( C++11 이상 )

    • std::Initializer_list

      • C++ 11이전에는 구조체/클래스/배열만 brace를 통한 초기화가 가능했다.
      • C++ 11에서 std::Initializer_list 템플릿 클래스를 통해 확장
      • initializer_list란 결국 임의의 타입의 임시 배열을 접근가능하게 하는 오브젝트
      • STL의 기본 컨테이너를 포함한 모든 클래스에서 사용가능
      • 특정 시점에서 자동 생성

        • braced-init-list가 리스트형 초기화에 사용되는 경우

          vector<int> v1{ 1, 2, 3, 4, 5 }; 
          
          //std::vector<int> v1(std::initializer_list<int>);  
        • braced-init-list가 auto 변수에 바인딩 되는 경우

          auto al = { 10, 11, 12 };  
          
          //std::initializer_list<int> al;  
    • Uniform Initialization

      • 기존의 C++에서 초기화 방식이 각 타입마다 제각각
      • Initializer_list를 활용하여 모든 오브젝트 타입에 대해 동일한 방식(brace-init-list)으로 초기화가 가능하다.
      • Uniform Initialization은 초기화 개량형을 통일하자는 Movement의 일종(?)
      • 구조체, 클래스, primitive, stl 컨테이너 등 모든 타입을 brace-init-list로 초기화

        struct AltStruct
        {
            AltStruct(int x, double y)    
            :    x_{ x }, y_{ y }    // 초기화 목록에서도 { } 로 초기화    
            {    } 
        
        private:    
            int x_;
            double y_;
        }; 
        
        AltStruct as{ 2, 4.3 }; 
        
        float b{ 1.f };
        
        map<int, int> m1
        {        
            { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }
        };
        
        //출처:[C++11] Uniform intialization
    • 위에서 언급한바 brace-init-list를 통한 초기화에서 auto의 경우 std::initializer_list로 타입 추론됨

      • 실제 이 추론은 2번의 타입 추론을 거쳐 가능
      • brace-init-list를 std::initializer_list로 변환하는 타입 추론
      • std::initializer_list템플릿을 인자에 맞는 타입으로 변경하는 타입 추론
    • 템플릿의 경우 auto와 같이 두번의 타입 추론을 하지 못함

      • 이유는?
      • Scott Meyers : "You might wonder why auto type deduction has a special rule for braced initializers, but template type deduction does not. I wonder this myself. Alas, I have not been able to find a convincing explanation."
      • 문제는 템플릿에 brace-init-list로 타입 추론을 하려하면, 에러가 발생한다는 것!
      auto x = {11, 23, 9}; //x's type is std::initialize_list<int>
      
      template<class T>
      void f(T param);
      
      f({11, 23, 9}); //error!
    • 이 차이점을 주의해야 하는 경우

      • Uniform Initialization을 auto에 적용시키려고 한 경우
        • 원하는 구조체, 클래스, 타입 대신 무조건 std::initializer_list로 타입 추론 될 것이다.
      • Uniform Initialization을 템플릿에 적용시키려고 한 경우
        • 에러 발생
      • C++14 에서
        • auto 리턴 함수의 경우
          • 이때 사용하는 auto의 추론방식은 template과 완전히 같다.
          • 때문에 brace-init-list를 사용하면 에러가 발생한다.
          auto createInitList()
          {
             return {1, 2, 3}; //error
          }
        • 람다의 패러미터로 auto를 사용하는 경우
          • 이 auto역시 template과 같은 방식으로 추론
          • 때문에 brace-init-list를 사용하면 에러가 발생한다.
          auto resetV = [&v](const auto& newValue){ v = newValue;};
          resetV({1, 2, 3}); //error