C++/Syntax 2021. 3. 10. 21:09

자료형 추론 (auto, decltype)

  • C++11

  • 자료형을 기본 타입 형태로 추론한다.

    • 자료형을 명시하기 복잡하거나 귀찮을 때 사용한다.

    • 가독성이 좋다.

  • 함수의 반환 값에도 적용할 수 있다.

    • Trailing Return Type(후행 반환 형식)

      • auto 키워드로 정의된 함수 반환 값의 자료형을 명시하는 것

      • 함수 앞에 자료형을 쓰는 것보다 가독성이 좋고, 인덴팅을 맞추기도 편하다.

      auto add(int x, int y)       -> int;
      auto add(double x, double y) -> double;

예제

iterator 대체

  • 개인적으로 이 때 제일 많이 사용하는 것 같다.

    #include <iostream>
    #include <vector>
    
    int        main()
    {
        using namespace std;
    
        vector<int> vec({ 1, 2, 3 });
    
        for (vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr)
            cout << *itr;
        cout << endl;
        for (auto itr = vec.begin(); itr != vec.end(); ++itr)
            cout << *itr;
        cout << endl;
        for (const auto & itr : vec)
            cout << itr;
        cout << endl;
    }
    
    /* stdout
    123
    123
    123
    */

기본 자료형 대입

  • 작동 원리는 템플릿 타입 추론처럼 컴파일할 때 값을 복사해서 인스턴스화한다고 생각하면 될 것 같다.

    int        main()
    {
        using namespace std;
    
        int             x = int();
        auto            auto_x = x;         // int
        const int&      crx = x;
        auto            auto_crx1 = crx;    // int
        const auto&     auto_crx2 = crx;    // const int &
        volatile int    vx = 1024;
        auto            avx = vx;           // int
        volatile auto   vavx = vx;          // volatile int
    }
    • auto_crx1의 경우 원본인 crxconst int& 자료형이므로 그 값을 R-value로 받아서 복사하는 것이다.

    • auto_crx2crxconst int& 자료형이므로 값이 int형이고, 따라서 const int&와 같아진다.

    • avx의 경우에도 vxR-value처럼 값만 복사하여 int 자료형이 되는 것이다.


템플릿 타입 추론 (Template Type Deduction)

  • auto 키워드와 비슷하게 자료형을 추론해낸다.

    template <typename T>
    void    func(T arg)
    {}
    
    int        main()
    {
        const int& crx = 123;
    
        func(crx);              // void func<int>(int arg)
    }
    template <typename T>
    void    func(const T& arg)
    {}
    
    int        main()
    {
        const int& crx = 123;
    
        func(crx);              // void func<int>(const int& arg)
    }

레퍼런스

  • auto &의 형태로 사용하면 L-value 레퍼런스를 참조하는 것이기 때문에 const 등의 속성이 유지된다.

    int        main()
    {
        volatile int   c = 0;
        auto&       rc = c;     // const int &
    }

L-value, R-value References

  • auto &&의 형태로 사용하면 상황에 따라 달라진다.

    int        main()
    {
      int        i = 42;
      auto&& ri_1 = i;        // int &
      auto&& ri_2 = 42;        // int &&
    }

const 포인터

  • 포인터의 const * 속성은 유지된다.

    • 원본은 건드리면 안된다는 속성을 유지해야 하기 때문이다.
    int        main()
    {
      int            x = 42;
      const int*    p1 = &x;
      auto        p2 = p1;    // const int*
    }

자료형이 정해지지 않은 변수 사용 예제

  • 템플릿에 어떤 자료형이 들어오는가에 따라 변수의 자료형이 달라지는데, auto로 간단하게 처리할 수 있다.

    template <typename T, typename S>
    void    func(T lhs, S rhs)
    {
      auto    prod1 = lhs * rhs;
    
      typedef decltype(lhs* rhs)    product_type;
    
      product_type prod2 = lhs * rhs;
    
      decltype(lhs * rhs) prod3 = lhs * rhs;
    }
    • product_typeauto 키워드가 등장하기 이전에 사용했던 방식이다.

    • decltype

      • 일부 컴파일러에서 관습적으로 사용하던 typeof를 공식적으로 내놓은 것이다.

자료형이 정해지지 않은 반환 값 정의 예제

  • autodecltype을 같이 사용하여 쉽게 해결할 수 있다.

    template <typename T, typename S>
    auto    func(T lhs, S rhs) -> decltype(lhs* rhs)
    {
      return lhs * rhs;
    }

auto와 decltype의 차이

  • decltype은 변수가 선언될 때의 자료형을 가져온다.

    • decltype 내부에 괄호가 감싸져있으면 L-value 레퍼런스로 가져온다.
    class S
    {
    public:
      int    x_;
    
      S()
      {
        x_ = 42;
      }
    };
    
    int        main()
    {
      int            x;
      const int    cx = 42;
      const int&    crx = x;
      const S*    p = new S();
    
      auto a = x;        // int
      auto b = cx;        // int
      auto c = crx;        // int
      auto d = p;        // const S*
      auto e = p->x_;    // int
    
      typedef decltype(x)            x_type;        // int
      typedef decltype(cx)        cx_type;        // const int
      typedef decltype(crx)        crx_type;        // const int&
      typedef decltype(p->x_)        x__type;    // int
    
      typedef decltype((x))        x_with_parens_type;            // int &
      typedef decltype((cx))        cx_with_parens_type;    // const int&
      typedef decltype((crx))        crx_with_parens_type;    // const int&
      typedef decltype((p->x_))    x__with_parens_type;        // const int&
    }
  • std::vector의 경우 [] 연산자 오버로딩이 L-value 레퍼런스를 반환하기 때문에 decltype이 레퍼런스로 잡힌다.

    #include <iostream>
    #include <vector>
    
    class S
    {
    public:
      int    x_;
    
      S()
      {
        x_ = 42;
      }
    };
    
    const S        foo()
    {
      return S();
    }
    
    const int&    foobar()
    {
      return 123;
    }
    
    int            main()
    {
      using namespace std;
    
      vector<int> vec = { 42, 43 };
    
      auto a = foo();                                // S
      typedef decltype(foo()) foo_type;                // const S
    
      auto b = foobar();                            // int
      typedef decltype(foobar()) foobar_type;        // const int&
    
      auto itr = vec.begin();                        // vector<int>::iterator
      typedef decltype(vec.begin()) iterator_type;    // vector<int>::iterator
    
      auto first_element = vec[0];                    // int
      decltype(vec[1]) second_element = vec[1];        // int&
    }
  • decltype에서 삼항연산자 등으로 값을 그대로 반환하게 될 경우 L-value 레퍼런스로 추론된다.

    int            main()
    {
      int x = 0;
      int y = 0;
      const int cx = 42;
      const int cy = 43;
      double d1 = 3.14;
      double d2 = 2.72;
    
      typedef decltype(x * y) prod_xy_type;    // int
      auto a = x * y;                            // int            
    
      typedef decltype(cx * cy) prod_xy_type;    // int
      auto a = cx * cy;                        // int
    
      typedef decltype(d1 < d2 ? d1 : d2) cond_type;    // double&
      auto c = d1 < d2 ? d1 : d2;                        // double
    
      typedef decltype(x < d2 ? x : d2) cond_type_mixed;    // double
      auto d = x < d2 ? x : d2;                            // double
    }
  • 위의 L-value 레퍼런스로 반환하는 특성때문에, std::remove_reference 구조체를 이용해야 할 때도 있다.

    • 반환 값이 L-value 레퍼런스일 때도 있고 R-value일 때도 있으면 헷갈리기 때문이다.
    #include <iostream>
    
    template <typename T, typename S>
    auto fpmin_wrong(T x, S y) -> decltype(x < y ? x : y)
    {
      return x < y ? x : y;
    }
    
    template <typename T, typename S>
    auto fpmin(T x, S y) ->
    typename std::remove_reference<decltype(x < y ? x : y)>::type
    {
      return x < y ? x : y;
    }
    
    int            main()
    {
      int        i = 42;
      double    d = 45.1;
      //auto    a = std::min(i, d); // 에러
      auto    a = std::min(static_cast<double>(i), d);
    
      int&    j = i;
    
      typedef decltype(fpmin_wrong(d, d)) fpmin_wrong_return_type1; // double&
      typedef decltype(fpmin_wrong(i, d)) fpmin_wrong_return_type2; // double
      typedef decltype(fpmin_wrong(j, d)) fpmin_wrong_return_type3; // double
    
      typedef decltype(fpmin(d, d)) fpmin_return_type1; // double
      typedef decltype(fpmin(i, d)) fpmin_return_type2; // double
      typedef decltype(fpmin(j, d)) fpmin_return_type3; // double
    }
  • decltype은 실제로 실행시키지 않고 추론만 하므로, 유효하지 않음에도 사용할 수 있는 경우가 있다.

    #include <vector>
    
    int            main()
    {
      std::vector<int> vec;
      typedef decltype(vec[0]) integer; // int&
    }
  • decltypenested type에 접근하기에도 유용하다.

    template <typename R>
    class SomeFunctor
    {
    public:
      typedef R result_type;
    };
    
    int            main()
    {
      SomeFunctor<int> func;
      typedef decltype(func)::result_type integer; // int
    }

lambda 함수에도 적용이 가능하다

  • 람다 함수 역시 decltype에서 소괄호로 한번 더 감싸주면 L-value 레퍼런스로 추론된다.

    #include <iostream>
    
    int            main()
    {
      using namespace std;
    
      auto lambda = []() { return 42; };
      decltype(lambda) lambda2(lambda);
      decltype((lambda)) lambda3(lambda);
    
      cout << &lambda << '\n' << &lambda2 << '\n' << &lambda3 << endl;
    }
    
    /* stdout
    0053F717
    0053F70B
    0053F717
    */
  • 람다 함수의 파라미터에는 auto 키워드가 올 수 있다.

    #include <iostream>
    
    int            main()
    {
      using namespace std;
    
      auto lambda = [](auto x, auto y)
      {
        return x + y;
      };
    
      cout << lambda(1.1, 2) << endl;
      cout << lambda(3, 4) << endl;
      cout << lambda(4.5, 2.2) << endl;
    }
    
    /* stdout
    3.1
    7
    6.7
    */

'C++ > Syntax' 카테고리의 다른 글

C++ 열거형 (Enumerate Type)  (0) 2021.03.10
C++ 형변환 (Type Conversion)  (0) 2021.03.10
C++ using  (0) 2021.03.10
C++ extern  (0) 2021.03.10
C++ 범위 지정 연산자 (Scope Resolution Operator)  (0) 2021.03.10