[C++] 자동으로 변수의 타입을 완성하는 auto 키워드

반응형

변수의 타입 추론( type deduction )

C++ 에는 모든 값에 해당하는 타입이 있습니다.

int main(){

    5;      // int
    3.14;   // double

    5L;     // long
    5.0f;   // float

    ?? variable{ 5 };   // variable의 타입 ??
}

예를 들어, 위의 3.14double 타입을 갖고, 5.0ffloat 타입을 갖고 있습니다.

그렇기 때문에, 프로그래머가 명시하지 않더라도, 컴파일러는 이를 이용해서 변수의 타입 예상할 수 있습니다.

 

이렇게, 컴파일러가 변수의 값으로부터 변수의 타입을 예상하는 것을 타입 추론( type deduction )이라고 하며, 이 추론의 결과를 이용하기 위해 auto 키워드를 사용합니다.

auto variable{ 5 };	// variable은 int로 추론됨

위에서 컴파일러는 값 5의 타입이 int임을 알고 있으므로, variable의 타입을 int로 추론합니다.

 

이렇게, auto 키워드는 변수의 타입을 추론하여 코드를 간결하고 읽기 쉽게 만들어 줄 뿐만 아니라, 이를 수정하는 경우에도 간편하게 만들어 줍니다.

struct Plain_Coordinate{
    int x{};
    int y{};
};

struct Polar_Coordinate{
    double radious{};
    double theta{};
};

int main(){

  Plain_Coordinate pt;

  auto pt2{ pt };	// pt2는 Plane_Coordinate 타입으로 추론됨
  auto pt3{ pt2 };
}

위에서, pt의 타입이 Plane_Coordinate 타입이므로, 컴파일러는 pt2의 타입을 Plane_Coordinate 타입으로 예상합니다.

이를 Plane_Coordinate pt2{ pt };로 정의하는 것보다, auto pt2{ pt };로 정의하는 것이 일반적으로 읽기 쉽습니다.

게다가, pt의 타입을 Polar_Coordinate 타입으로 변경할 경우, 위에서는 pt를 정의하는 문장만을 수정해도, 다른 모든 변수의 정의를 수정하게 되는 효과를 얻을 수 있습니다.

int main(){

  Polar_Coordinate pt;

  auto pt2{ pt };	// pt2는 Polar_Coordinate 타입으로 추론됨
  auto pt3{ pt2 };
}

 

C++ 에는 다양한 값들과 그만큼의 타입이 있습니다.

여기서는 이러한 값들로부터 C++은 변수의 타입을 어떻게 추론하는지 알아보겠습니다.

 

표현식의 값으로 추론

C++은 표현식을 평가하여, 평가된 값의 타입으로 변수의 타입을 추론합니다.

int add( int x, int y){
    return x + y;
}

void empty(){
    // do something
}

int main(){

    auto var1{ 1 + 2 };	    // int로 추론
    auto var2{ 1 + 3.14 };	// double로 추론

    auto var3{ add( var1, var2 ) }; // int로 추론

    auto var4;      // error
    auto var5{};    // error 
    auto var6{ empty() };   // error. 반환값이 없음
}

위의 var3와 같은 경우, 함수 add의 반환값을 평가하여 변수의 타입( 여기선 int 타입 )을 정합니다.

그렇기 때문에, var4의 정의와 같이 초기값을 지정하지 않거나, var5var6과 같이 표현식의 값을 평가할 수 없는 경우는 오류입니다.

 

문자열

C-style 문자열 리터럴( literal )의 타입은 const char 배열 타입입니다.

#include <string_view>  // for std::string_view

int main(){

    auto str{ "This is string" };   // const char* 타입

    using namespace std::string_literals;	// for string 리터럴
    using namespace std::string_view_literals;	// for string_view 리터럴

    auto str2{ "This is C++ string"s };         // std::string 타입
    auto str3{ "This is C++ string_view"sv };   // std::string_view 타입
}

위에서 "This is string"의 타입은 const char[15]입니다. ( C-style 문자열의 마지막은 '\0'로 끝납니다. )

 

[C++] C-style 문자열의 특징

C-style 문자열C++을 처음 배울 때, 대부분 아래와 같은 예문을 보게 됩니다.#include int main(){ std::cout 그리고, 이 예문에 사용된 "Hello, world!"가 C-style 문자열입니다. 이것을 좀 더 정확하게 정의하자

codingbonfire.tistory.com

그렇지만, 이 타입을 함수에 전달하게 되면 인자의 복사 문제나, 배열의 크기에 따른 매개변수 타입 문제가 발생하기 때문에, C++에서는 이 타입을 암시적으로 const char* 타입으로 전환합니다.

이런 관계로, 컴파일러는 C-stype 문자열을 const char* 타입으로 추론합니다.

 

그리고, C++은 std::string 타입의 문자열( s 접미사를 붙여 표시 )과 std::string_view 타입의 문자열( sv 접미사를 붙여 표시 )도 지원하는데, 이 경우 컴파일러는 각각의 타입으로 추론합니다.

 

[C++] 문자열을 읽기 전용으로 참조하는 std::string_view

std::string_viewstd::string_view는 문자열을 사용하면서 발생하는, 무거운 복사 과정을 줄이고자 만든 클래스입니다. C++ 17 이상의 버전에서 사용할 수 있는, 이 클래스는 실제 원본 문자열을 읽기 전용

codingbonfire.tistory.com

 

const와 constexpr

const 키워드를 auto와 같이 사용하여, 변수를 const 타입으로 추론하도록 만들 수 있습니다.

그리고, 이러한 방식은 constexpr 키워드를 사용할 때도 동일합니다.

int main(){

    int var{ 5 };

    const auto const_var{ var };        // const int 타입
    constexpr auto constexpr_var{ 5 };  // constexpr int 타입
    
    constexpr auto constexpr_var2{ var };	// error !
}

위에서 var의 타입이 int이므로, const_varint 타입으로 추론되는데, 앞에 const가 있으므로, 이 변수는 const int 타입으로 추론됩니다.

 

참고로, constexpr 변수는 상수 표현식( constant expression )으로 초기화되어야 합니다.

 

[C++] 컴파일 시에 값을 알 수 있는 constexpr 변수

constexpr 변수이전 글에서, 상수 표현식( const expression )은 컴파일 시에 평가할 수 있는 표현식을 말한다고 얘기했었습니다. [C++] 컴파일 시 값을 알 수 있는 상수 표현식( const expression )컴파일 시,

codingbonfire.tistory.com

▼출력

error: the value of 'var' is not usable in a constant expression

그러므로, constexpr_var2constexpr int 타입으로 추론되지만, var가 상수 표현식이 아니므로 오류입니다.

 

그런데, 반대로 const 타입으로부터 auto 변수를 추론할 때는, const 속성을 제거하고 추론합니다.

int main(){

    const int var{ 5 }; // const 변수

    auto var2{ var };               // int 타입
    const auto const_var{ var };    // const int 타입
    
    constexpr auto constexpr_var{ var };    // constexpr int 타입
}

위에서 var의 타입은 const int 타입입니다. ( const는 타입의 일부분입니다. )

그래서, var2의 타입은 const int 타입일 것 같지만, 컴파일러는 int 타입으로 추론합니다.

이것은 varconst 변수라고 해서, var의 값으로 초기화되는 var2 변수가 반드시 const 변수가 될 이유가 없기 때문입니다.

즉, varvar2 변수는 서로 독립적인 객체라는 것입니다.

 

만약, var2의 타입을 const 타입으로 추론하고 싶다면, const_var처럼 auto 앞에 const를 추가하면 됩니다.

그리고, const auto와 마찬가지로, constexprauto 앞에 추가하면 constexpr_varconstexpr int 타입으로 추론합니다.

 

참고로, const 정수형 타입( integral type, 예를 들어 const int ) 변수가 상수 표현식( 여기서는 5 )으로 초기화되면, 이 변수 또한 상수 표현식입니다.

따라서, 이전 예제와 달리 constexpr_var가 상수 표현식 var로 초기화되므로, 위의 마지막 문장은 합법입니다.

int main(){

    const int var{ 5 }; // const 변수
    constexpr auto constexpr_var{ var };    // var는 상수 표현식

    auto var2{ var };               // int 타입
    decltype( var ) var3{ var };    // const int 타입
}

그리고, 위의 decltype은 이름이나 표현식의 구체적인 형식( declared type )을 알려주는 키워드입니다.

 

[C++] 표현식의 구체적인 타입을 알려주는 decltype

decltype 키워드decltype는 이름이나 표현식의 구체적인 타입( declared type )을 알려주는 키워드입니다.이 키워드는 다음과 같은 규칙에 따라, 표현식의 타입을 반환합니다. ● 표현식이 식별자 또는

codingbonfire.tistory.com

그래서, 변수 var의 타입이 const int이므로, decltype( var )const int 타입이 됩니다.

 

그런데, 위의 마지막 문장같이 decltype을 사용해서 var3를 정의하려면, var를 두 번 중복해서 사용해야 합니다.

이 경우는 var3를 초기화하는 var로부터 구체적인 타입을 구하는 것이므로, 이러한 경우에도 auto를 사용할 수 있습니다.

const int var{ 5 };
decltype( auto ) var3 { var };	// const int 타입

컴파일러는 위의 decltype( auto )를 변수의 초기값 var의 구체적인 타입 const int 타입으로 추론합니다.


constexprconst와 달리 타입의 일부분이 아닙니다.

그렇지만, constexpr 타입은 암시적으로 const 타입이므로, constexpr 타입의 변수로부터 변수의 타입을 추론하면, const와 마찬가지로 constexpr을 제외한 타입으로 추론합니다.

int main(){

    constexpr int var{ 5 }; // constexpr 변수
    
    auto var2{ var };       // int 타입
    
    const auto const_var{ var };    // const int 타입
    constexpr auto constexpr_var{ var };    // constexpr int 타입
}

그래서, var의 타입은 const int 타입이지만( constexpr int 타입이 아닙니다. ), var2의 타입은 int로 추론됩니다.

만약, constconstexpr 변수로 추론하고 싶다면, 위와 같이 constconstexpr을 추가해야 합니다.

 

포인터( pointer )

포인터는 이것이 가리키는 대상이 따로 있다는 점에서 참조( reference )와 비슷하지만, 이 포인터 자체는 변수라는 점을 잊으면 안 됩니다.

따라서, 컴파일러는 다른 변수와 같은 방식으로 포인터의 타입을 추론합니다.

int main(){
    
    int var{ 5 };
    auto var2{ var };    // int 타입

    int* pVar{ &var };
    auto var3{ pVar };  // int* 타입

    const int* pcVar{ pVar };
    auto var4{ pcVar }; // const int* 타입
}

위에서, var2var의 타입인 int로 추론하는 것과 같이, pVar의 타입이 int* 타입이므로, var3의 타입을 int* 타입으로 추론합니다.

그리고, const int*와 같이, const 객체를 대상으로 하는 포인터( pointer to const )는 일반 포인터에 대입할 수 없습니다.

 

[C++] const 포인터( pointer )의 종류

포인터의 상수성( constness )포인터( pointer )는 객체의 주소를 값으로 하는 변수입니다.그리고, 이 포인터는 역참조( dereference ) 연산자 *를 사용하여, 저장하고 있는 주소에 있는 객체의 값을 변경

codingbonfire.tistory.com

따라서, 컴파일러는 var4의 타입을 int* 타입이 아니라 const int* 타입으로 추론해야 합니다.

 

하지만, const 포인터는, 위에서 설명한 const 변수로부터 타입을 추론할 때와 마찬가지로, const 속성을 제거하고 추론합니다.

int main(){

    int var{ 5 };

    int* const pcVar{ &var };
    auto var5{ pcVar};  // int* 타입
    
    const auto cvar5{ pcVar };	// int* const 타입
    auto const cvar5_1{ pcVar }; // int* const 타입

    const int* const pcVarToConst{ &var };
    auto var6{ pcVarToConst };  // const int* 타입
}

위에서, pcVarint* const 타입의 const 포인터라고 해서, var5const 포인터가 되어야 할 필요는 없습니다.

따라서, var5의 타입을 int* 타입으로 추론합니다.

만약, var5의 타입을 const 포인터인 int* const 타입으로 추론하고 싶다면, cvar5처럼 앞에 const를 붙이면 됩니다.

이때, auto const와 같이 const를 뒤에 붙여도 마찬가지가 되는데, 이것은 int 타입으로 추론되는 변수 앞/뒤에 const를 붙이는 것을 떠올리면 쉽게 이해할 수 있습니다.

int var{ 5 };
const auto cvar{ var };	// const int 타입
auto const cvar_1{ var }; // const int 타입

 

그리고, 대상이 const 객체인 const 포인터 pcVarToConst로부터 추론되는 var6의 타입은, 대상이 const 객체인 포인터 타입( pointer to const )인 const int*입니다.

이것은, const int* const 타입에서 뒤의 const 속성만 제거된 타입입니다.

 

auto*

auto 변수는 다양한 타입의 값으로 초기화될 수 있습니다.

하지만, auto* 변수는 포인터 타입으로만 초기화되어야 합니다.

따라서, auto* 형태의 변수를 정의하는 것으로 이 변수가 포인터( pointer )라는 것을 강조할 수 있습니다.

int main(){

    int var{ 5 };
    
    auto var2{ var };   // int 타입
    auto var3{ &var };  // int* 타입

    auto* var4{ var };  // error. 포인터 타입만 가능
    auto* var5{ &var }; // int* 타입
}

▼출력

error: unable to deduce 'auto*' from 'var'

위에서, var2 같은 경우 int 타입의 값이든, 포인터 타입의 값이든 주어진 값으로부터 var2의 타입을 추론할 수 있습니다.

하지만, var4와 같은 auto* 타입의 변수는 포인터 타입의 값으로만 초기화되어야 합니다.

따라서, int 타입인 varvar4를 초기화하는 문장은 오류입니다.

 

그리고, const 객체를 가리키는 포인터( pointer to const )로부터 추론할 수 있는 auto*의 타입은 오직 하나뿐입니다.

int main(){

    int var{ 5 };
    
    const int* pPtrToConst{ &var };	// const 객체에 대한 포인터
    auto* var6{ pPtrToConst };  // const int* 타입
    const auto* var7{ pPtrToConst };    // const int* 타입

    int* const pConst{ &var };	// const 포인터
    auto* var8{ pConst };   // int* 타입
    auto* const var9{ pConst }; // int* const 타입
}

위에서, pPtrToConst 같이, const int* 타입의 변수를 대입할 수 있는 포인터 타입은 const int* 뿐입니다.

따라서, var6의 타입은 const int* 타입이 될 수밖에 없습니다.

var7의 정의에 사용된 const는 이 변수가 ( 원래도 그렇지만 한번 더 ) const 객체에 대한 포인터임을 강조하는 것에 불과합니다.

 

하지만, var8의 경우, const 포인터의 const 속성은 제거되고 추론되기 때문에, int* 타입으로 추론됩니다.

그러므로, const 포인터로 추론하고 싶다면, var9와 같이 auto* 뒤에 const를 추가해주어야 합니다.

 

참조( reference )

참조는 포인터( pointer ) 같은 변수가 아니라 참조하는 대상에 대한 별명입니다.

그리고, const 변수로부터 초기화되는 변수가 const 타입일 필요가 없는 것과 마찬가지로, 참조로부터 초기화되는 변수가 참조 타입일 필요가 없습니다.

따라서, 컴파일러는 auto를 참조 타입이 아니라, 참조가 가리키는 대상의 타입으로 추론합니다.

int main(){

    int var{ 5 };
    int& ref{ var };

    auto var2{ ref };   // int 타입
    
    const int& const_ref{ var };
    auto var3{ const_ref }; // int 타입
}

위에서, 컴파일러는 ref로 초기화되는 var2의 타입을 int& 타입이 아니라, int 타입으로 추론합니다.

그리고, const_ref로 초기화되는 var3 변수도 const 속성과 참조 타입이 아닌 타입으로 추론하므로, int 타입으로 추론됩니다.

 

따라서, 참조 타입으로 추론하고 싶다면, auto*를 포인터 타입으로 추론하는 것과 같이, auto& 타입을 사용해야 합니다.

int main(){

    int var{ 5 };
    int& ref{ var };
    const int& const_ref{ var };

    auto& var4{ ref };  // int& 타입
    auto& var5{ const_ref };    // const int& 타입
    const auto& var6{ ref };    // const int& 타입
}

위에서 auto&는 초기화되는 변수의 타입인 참조 타입인 것과 관계없이, 참조 타입으로 추론합니다.

그리고, const 객체로 초기화될 수 있는 참조 타입은 const 참조 타입뿐입니다.

따라서, const_ref로 초기화되는 var5의 타입은 const int& 타입입니다.

만약, 초기화되는 변수가 const 참조 타입을 강조하고 싶다면, var6의 정의와 같이 auto 앞에 const를 붙이면 됩니다.

 

 

정리

  • 컴파일러는 변수가 초기화되는 값을 통해 auto의 타입을 추론합니다.
  • const 객체로 초기화되는 auto 변수는 반드시 const 객체일 필요가 없으므로, 컴파일러는 이 변수의 타입을 const 속성이 제거된 타입으로 추론합니다.
  • auto 변수가 포인터( pointer ) 타입임을 강조하고 싶다면 auto* 타입으로 추론해야 합니다.
  • 참조( reference ) 타입으로 초기화되는 auto 변수는 참조 타입일 필요가 없으므로, 컴파일러는 이 변수의 타입을 참조 타입이 아닌 타입으로 추론합니다.
  • auto 변수를 참조 타입으로 추론하고 싶다면, auto& 타입으로 추론해야 합니다.

 

 

이 글과 관련 있는 글들

함수의 타입 추론과 후행 반환 타입을 위한 auto 키워드

이름 충돌을 해결하기 위한 namespace

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유