반응형

Auto 키워드

 auto 키워드는 선언된 대상의 초기화 표현을 컴파일러에게 추론을 지시합니다.

 auto를 사용할 때 const, volatile, 포인터(*), 참조(&), rvalue 참조 지정자를 함께 사용 할 수 있습니다.

auto의 장점

 auto를 사용하면 복잡한 타입의 변수를 쉽게 선언 할 수있습니다. 

 예를 들어서 멤버 포인터, 함수 포인터, 템플릿 형식을 포함하는 초기화 표현식을 선언 할 때 유용합니다.

class Math
{
public:
    int Sum(int left, int right)
    {
        return left + right;
    }
};

int Sum(int a, int b)
{
    return a + b;
}


// 함수 포인터의 타입
int (*SumFunc1)(int, int) = Sum;
// 함수 포인터의 타입을 auto를 사용
auto SumFunc2 = Sum;

// 멤버 함수 포인터 타입
Math math;
int (Math:: * MathSum1)(int,int) = &Math::Sum;
(math.*MathSum1)(5, 5);
// 멤버 함수 포인터 타입을 auto 사용
auto MathSum2 = &Math::Sum;
(math.*MathSum2)(5, 5);


std::map<int, int> mapContainer;
// 템플릿 형식의 타입
std::map<int, int>::iterator it1 = mapContainer.begin();
// 템플릿 형식의 타입을 auto를 사용
auto it2 = mapContainer.begin();

 예제를 보시면 함수 포인터나 템플릿 타입의 경우 작성이 어려우며 오타가 날 가능성이 높습니다.

 auto를 사용하면 코드를 간결하게 유지 가능하기 때문에 auto를 사용할 것을 권장합니다.

    std::map<int, int> mapContainer;
    std::vector<int> vecContainer;
   
    // auto it = mapContainer.begin()에서 
    // 아래와 같이 변경되어도 코드의 큰 수정 없이 사용 할 수 있습니다.
    auto it = vecContainer.begin();

 또한 auto의 대상이 변경되어도 최소한의 수정으로 구현이 가능하도록 지원합니다. 

auto사용시 참조 및 cv-qualifiers 삭제

 auto를 사용해서 타입을 추론 할 때 참조 및 const 지정자, volatile 지정자가 삭제 되는 것에 유의 해야 합니다.

    cout << "[auto] Check reference "<< endl;
    int cnt = 10;
    int& cntRef = cnt;
    // auto로 int& 타입을 추론하면 int
    auto Auto_cntRef = cntRef;
    Auto_cntRef += 5;
    cout << "   Auto_cntRef = " << Auto_cntRef << endl;
    cout << "   cntRef = " << cntRef << endl;

    cout << "[auto] Check const " << endl;
    const int const_cnt = 100;
    // auto로 const int 타입을 추론하면 int
    auto Auto_const_cnt = const_cnt;
    Auto_const_cnt++;
    cout << "   Auto_const_cnt = " << Auto_const_cnt << endl;

    cout << "[auto &] Check " << endl;
    cnt = 20;
    // auto & 명시적으로 지정하면 참조 타입으로 추론
    auto & Auto_countRef_ref = cnt;
    Auto_countRef_ref += 5;
    cout << "   Auto_countRef_ref = " << Auto_countRef_ref << endl;
    cout << "   cnt = " << cnt << endl;

   
    cnt = 30;
    // const auto 명시적으로 지정하면 const 타입으로 추론
    const auto Auto_countRef_const = cnt;
    Auto_countRef_const++;  // error! const 변수를 수정할 수 없습니다. 

결과 화면

 위의 예제를 보면 Auto_cntRef의 경우 auto를 사용해서 int & 타입을 추론 했는데 int로 추론되었습니다. 

 auto를 사용해서 추론시 참조가 삭제되는 것을 확인 할 수 있습니다. 아래의 Auto_const_cnt의 경우에도 

 추론시 const가 삭제되는 것을 확인 할 수 있습니다.

 auto를 통해서 참조나 const, volatile 지정자를 설정하려면 명시적으로 "auto &", "const auto", "volatile auto"로

 지정 해야 합니다.  처음 auto를 접할 때 자주 실수하는 부분이니 명확히 이해하고 넘어가는 것이 중요합니다.

class Employee
{
public:
    void GiveSalary(int m)
    {
        m_Money += m;
    }
    int GetMoney()
    {
        return m_Money;
    }

private :
    int m_Money;
};

// 월말에 직원들에게 월급을 지급합니다.
void EndMonth()
{
    std::vector<Employee> vecEmployees;
    // .. 생략

    // for (auto& employee : vecEmployees) 으로 수정해야 정상적으로 지급됩니다.
    // for (auto employee : vecEmployees) 값 복사가 발생합니다. 
    for (auto employee : vecEmployees)
    {
    	// 직원들을 순회하면 만원씩 지급합니다. 
        employee.GiveSalary(10000);
    }
}

 위의 예제는 for문 수행시 auto를 사용하는 부분인데 auto에 &을 누락하여 값 복사가 발생되는 로직입니다.

auto 연역 규칙

 auto의 일반적인 연역 규칙은 템플릿 인자의 연역 규칙과 거의 일치 합니다.

 아래에는 auto를 통해서 특별하게 추론되는 연역에 대해서 설명 드립니다. 

보편 참조

 auto && 형태로 선언을 하게 되면 해당 형식은 컴파일러에 의해서 보편 참조로 추론됩니다. 

 보편 참조란 문맥에 따라서 다양하게 형식 연역이 되는 타입을 뜻 합니다.

const int i = 10;

auto&& var1 = i; // 보편참조 = const int &으로 연역

const auto&& var2 = i; // const auto&&은 보편 참조 아님 error

auto&& var3 = 12; // 보편 참조 = int && 으로 연역

컴파일러를 통한 확인

 예제를 보면 var1은 const int 타입인 i를 전달 받아서 const int &으로 추론됩니다. 

 var2은 const auto && 선언되었기 때문에 const에 의해서 보편 참조가 아닌 const rvalue 참조로 인식됩니다. 

 var3은 int 상수 값을 전달받아서 int &&로 연역됩니다.

문자열 연역 규칙

 문자열을 auto로 선언해서 타입을 추론하면 const char * 타입으로 연역되는 것을 확인 할 수 있습니다.

 하지만 auto &로 선언해서 타입을 추론하면 const char &[크기] 형태로 연역되는 것을 확인 할 수 있습니다. 

const char name[] = "ahn jung woong";

auto var1 = name;

auto & var2 = name;

컴파일러를 통한 확인

초기화 리스트 연역 규칙

 초기화 리스트에 auto를 사용하게 되면 사용자의 바램과는 다르게 동작 할 수 있습니다. 

    // int 로 연역됨
    auto var1 = 2;
    // std::initializer_list<int>로 연역됨
    auto var2 = { 2 };
    // error 발생 var2는 int가 아님
    var2 = var1;

 예제의 var2에 auto를 선언한 사용자는 int로 추론 되기를 원하지만 std::initializer_list<int>로 추론되기 때문에

 int처럼 사용 되지 않습니다. 

Decltype 

 decltype 타입 지정자는 명시된 대상의 타입을 산출합니다. 

    int cnt = 100;
    int& refcnt = cnt;
    
    // 컴파일러는 decltype(refcnt) = int &로 추론합니다.
    decltype(refcnt) decl_refcnt = refcnt;
    refcnt += 5;
    std::cout << "cnt = " << cnt << endl;
    std::cout << "decl_refcnt = " << decl_refcnt << endl;

    // 컴파일러는 decltype(const_cnt) = const int 로 추론합니다.
    const int const_cnt = cnt;
    decltype(const_cnt) decl_const_cnt = const_cnt;
    // 에러 const int 수정 불가
    decl_const_cnt++; 

 decltype(대상) 형태를 제공하면 대상의 타입을 추론해서 리턴합니다. 

struct A
{
public:
    int a;
};

{    
    const A x;
    decltype(x.a) AxEntity = 10;
    // decltype(x.a) => int로 추론
    AxEntity = 100;

    decltype((x.a)) AxExpression = 10;
    // decltype((x.a)) => const int로 추론
    AxExpression = 100;
}

 decltype은 한가지 규칙이 있는데요 decltype 호출시 내부 대상을 괄호로 감싸면 표현식으로 추론합니다.

 괄호가 없다면 대상의 객체 자체의 타입을 추론합니다. 

 const A로 선언된 x의 a 멤버변수를 decltype으로 지정하는 예제를 볼수 있습니다. 

decltype 선언 구조 내  용
decltype(대상)

대상의 객체로 인식해서 추론합니다. 

예제에서는 x.a를 객체로 인식하여 a가 선언된 타입인 int로 추론됩니다. 

decltype((대상))

대상을 괄호로 한번 감싸면 표현식으로 인식해서 추론합니다. 

예제에서는 x.a를 표현식으로 인지해서 x의 객체가 const A이기 때문에 

 (x.a)는 const int로 추론됩니다. 

지원

 decltype은 c++11, visual studio 2010에서부터 지원됩니다. 

 decltype(auto)은 c++14, visual studio 2015부터 지원됩니다. 

Auto키워드와 같이 Template에서 사용

 decltype 타입 지정자는 auto 키워드와 함께 사용하면 템플릿 라이브러리를 작성할 때 유용하게 사용 할 수 있습니다.

 템플릿 인자의 타입을 기반으로 리턴 값 타입이 정해지는 템플릿 함수에서 auto와 decltype 키워드를 같이 사용해

 리턴 타입을 정의 할 수 있습니다.

 예제를 통해서 설명 드리겠습니다. 

// c++ 11에서의 decltype 구현
template<typename T1, typename T2>
auto TemplatePlus(T1&& right, T2&& left) -> decltype(right + left)
{
    return right + left;
}

// c++ 14에서의 decltype 구현
template<typename T1, typename T2>
decltype(auto) TemplatePlus(T1&& right, T2&& left)
{
    return right + left;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 100;
    // TemplatePlus(10.3, a) 리턴 타입은 double로 추론됩니다.
    auto value = TemplatePlus(10.3, a);
}


// visual studio 2019에서는 이렇게 선언해도 수행가능
template<typename T1, typename T2>
auto TemplatePlus(T1&& right, T2&& left)
{
    return right + left;
}

 예제를 보면 TemplatePlus 템플릿 함수는 템플릿 인자 T1, T2에 의해서 리턴값이 정해지는 함수입니다.

 decltype과 auto를 통해서 컴파일 타임에 T1, T2로 구성된 리턴 값의 타입을 추론하도록 컴파일러에게

 지시할 수 있습니다.

 예제를 보면 c++11과 c++14에서의 구현 모습이 다른 것을 확인 할 수 있습니다.

 c++ 11에서는 명시적으로 리턴 타입의 표현식을 후행 반환 타입에 기입 해야 했는데

 c++ 14부터는 decltype에 auto를 추가해서 간결하게 사용가능하도록 지원되었습니다. 

 decltype 키워드는 auto와 함께 템플릿 함수의 리턴값을 추론하는데 많이 사용되고 있습니다.

반응형

'c++ > modern c++' 카테고리의 다른 글

[c++17] string_view  (0) 2020.03.21
[c++] constexpr 키워드  (0) 2020.03.19
[c++] std::move와 std::forward  (4) 2020.03.07
[c++] Move semantics  (0) 2020.03.07
[c++] 람다(Lambda) 표현식  (0) 2020.03.04

+ Recent posts