
auto
C++에서는 모든 객체에 대해 명시적인 타입이 제공되어야 한다
float f{ 10.f };
위 코드는 float타입의 변수 f를 float타입의 literal 값인 10.0으로 초기화 한 코드이다
결국 float이라는 동일한 타입 정보를 두 번 제공하는 셈이다
C++에서 타입 추론(Type Deduction)은 컴파일러가 초기화값을 기반으로 해당 객체의 타입을 추론하는걸 의미한다
이때 타입을 auto 키워드로 사용한다
auto f{ 10.f }; //f는 float로 타입 추론이 된다
auto i{ 1 + 3 }; //i는 int로 타입 추론이 된다
C++17 이전에는 auto d{ 10.0 }은 double이 아닌 std::initializer_list< double >로 추론되었지만 수정되었다
C++14 이하 버전에서 auto를 사용할 시 { }가 아닌 =으로 복사초기화를 해야 한다
물론 변수의 타입에만 사용할 수 있는게 아니라 함수의 반환형에도 사용이 가능하다
auto add(int a, int b)
{
return a + b;
}
//add()의 반환형은 int로 추론된다
auto를 사용할 때 literal suffix를 사용하여 특정 타입을 명확하게 지정할 수 있다
ex) f, u
또한 auto를 사용할 때 const, constexpr도 사용이 가능하다
const auto a{ 10 };
constexpr auto b{ 10.f };
auto를 사용하기 위해서는 반드시 초기화값이 존재해야 한다
void foo()
{
}
auto a;
auto a{ };
auto a{ foo() };
//위의 3케이스 전부 사용 불가
auto 변수에 const값을 할당하게 되면 const는 제거된다, 따라서 명시적으로 auto에 const를 붙혀줘야한다
const int i{ 10 };
auto a{ i }; //const가 아님
const auto a{ i }; //이렇게 const를 명시해야함
단 C-Style의 문자열로 초기화 시 객체의 타입이 const char*이기 때문에 auto타입 변수에 초기화했을때 string이 나오지 않는다
auto s{ "Kelvin" }; //auto는 const char* 타입으로 추론된다
std::string이나 std::string_view 타입으로 추론 시키려면 s, sv suffix를 사용해야 한다
#include <string>
#include <string_view>
using namespace std::literals; //s, sv suffix 사용을 위해 필요한 namespace
auto s{ "Kelvin"s }; //std::string으로 추론
auto s{ "Kelvin"sv }; //std::string_view로 추론
constexpr은 타입 시스템이 아니다, 단순히 값이 컴파일타임에 계산된다는걸 보장해주는 키워드이다
따라서 auto의 타입 추론 시스템에서 유지되지 않는다 (const로 유지됨)
constexpr double a{ 10.5 };
auto temp{ a }; //a
const auto temp{ a }; //const
constexpr auto temp{ a }; //constexpr
auto의 장단점
장점
우선 코드가 더 정렬되어 보인다 (각각 다른 타입 대신 auto를 사용하면 정렬되어 보임, 하지만 장점이라고 해야할까..?)
초기화가 강제되어 UnInitialized 이슈를 방지할 수 있다
불필요한 타입변환이 방지된다
#include <string_view>
std::string_view foo()
{
}
auto s{ foo() }; //따로 불필요한 타입변환 없이 바로 std::string_view 타입으로 추론된다
단점
함수에서의 타입 추론
int add(int a, int b)
{
return a + b;
}
위 함수가 컴파일될 때 컴파일러는 a + b가 int라는것을 결정하고 반환값의 타입이 함수 정의에 작성한 반환형과 일치하는지 혹은 타입변환이 가능한지 확인한다
C++14부터 auto를 함수의 반환형에 사용하여 타입추론이 가능해졌다
auto add(int a, int b)
{
return a + b;
}
컴파일러가 return을 분석하여 반환형을 결정하여 타입추론을 한다 (위 예시에서는 int로 추론)
함수에서 auto를 사용할때는 return타입이 전부 같아야한다
auto foo(bool b)
{
if (b)
{
return 5;
}
else
{
return 5.5f;
}
}
//compile error
반환형이 복잡하거나 예상하기 힘든경우 auto를 사용하면 간결한 함수를 만들 수 있다 (마찬가지로 프로그래머가 타입을 명확하게 인지하기 힘들다 따라서 반환형을 auto가 아닌 명확히 작성하는걸 권장한다)
반환형이 auto인 함수는 전방선언만으로는 사용이 불가능하다, 정의가 해당파일에 존재해야 한다
auto add(int, int);
int main()
{
add(10, 20);
return 0;
}
auto add(int a, int b)
{
return a + b;
}
//compile error

따라서 헤더파일에 auto함수를 선언만하고 include하여 사용하는건 불가능하다 (정의까지 .h에 있고 static으로 선언하거나 inline으로 처리하면 사용가능)
Trailing Return Type (후행 반환 타입)
후행 반환 타입을 사용하면 auto는 타입 추론을 하지 않고 후행 반환 타입을 사용하기 위한 키워드가 된다
auto foo(int a, int b) -> int
{
return a + b;
}
//auto는 int를 사용하기 위한 키워드가 됨
후행 반환 타입은 긴 반환형을 가독성 좋게 만들 수 있으며 타입은 명확하게 확인할 수 있게 한다
함수의 반환형이 매개변수의 타입에 따라 달라질 경우 후행 반환 타입을 사용하면 좋다
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> std::common_type_t<T1, T2>
{
return a + b;
}
//or
auto add(int x, double y) -> std::common_type_t<decltype(x), decltype(y)>;
//이와 같은 코드는 auto를 사용하지 않으면 컴파일 에러가 발생하기 때문에 후행 반환 타입을 사용하여 반환타입을 결정한다
decltype()
decltype(변수 or 표현식)은 해당 변수나 표현식의 타입을 컴파일 타임에 추론해준다
int x = 10;
double y = 5.5;
decltype(x) a; // a는 int로 추론
decltype(y) b = 3.14; // b는 double로 추론
C++20 이전에는 함수의 매개변수에 auto를 사용할 수 없었지만 C++20이후에는 매개변수에 auto사용이 가능하다, 하지만 타입 추론으로서의 기능이 아닌 함수 템플릿 축약 문법으로 동작한다
이는 다음과 같다
void printValue(auto value)
{
std::cout << value << std::endl;
}
template <typename T>
void printValue(T value)
{
std::cout << value << std::endl;
}
//이 두 함수는 같은 동작을 한다
일반적으로 반환형이 복잡하거나 변경 가능성이 높은경우 auto를 사용한다 (보통 명시적으로 작성하는게 좋음)