2014년 1월 3일 금요일

C++11 Feature 정리 - auto, trailing(suffix) return type

이전 블로그에서 이전 함 (원본 글 2014/01/03 작성)

일단 Code Project의 "Ten C++11 Features Every C++ Developer Should Use"를 기반으로 
C++11 feature들에 대해서 대충 정리 함.


[auto - deduction of a type from an initializer]

Deducing the type of variable from its initializer expression

말 그대로 생성자를 기반하여 변수의 type을 추정하게 하는 키워드. compile 시간에 사용 가능.
위 링크에서의 예제를 보면 바로 이해가 될 것임.
auto x = 7;                       // int 
auto x = expression;         // expression의 결과값의 type

auto v2 = 'c'                    // char
auto v3 {f()}                    // f()의 리턴값의 type
auto x0 {}                       // error
auto x3 {1,2,3}                // 3개의 값을 가진 int list

템플릿 인자를 다룰 때 유용하게 사용되어짐.

C++11
  template<class T> void printall(const vector<T>& v)
{
for (auto p = v.begin(); p!=v.end(); ++p)
cout << *p << "\n";
}

In C++98, we'd have to write

template<class T> void printall(const vector<T>& v)
{
for (typename vector<T>::const_iterator p = v.begin(); p!=v.end(); ++p)
cout << *p << "\n";
}

하지만 단순히 간단히 타이핑 하는 것으로 끝나는 것이 아니라 
템플릿 인자를 사용해서 프로그래밍할 경우 좀 애매한 경우에 유용하다.
템플릿 인자를 그대로 사용할 때는 괜찮지만 템플릿 인자들을 연산해서 나오는 결과가 무엇인지 몰라 구체화 하지 못할 경우일때가 하나의 예이다.

 template<class T, class U> void multiply(const vector<T>& vt, const vector<U>& vu)
{
// ...
auto tmp = vt[i]*vu[i];
// ...
}

그리고 해당 링크에서 보면 auto feature는 1984에 개발되었지만 C compatibility 문제로 인해서 제외가 되었었다고 함. 하지만 이전 auto의 개념("this is a local variable")은 지금과 다르다고 하다. (하지만 난 history도 C++98도 몰라서.. 자세히는 모르겠다.)

추가로 trailing(suffix) return type 예를 codeproject 링크에서 보여주고 있음.

 template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
{
   return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

trailing(suffix) return type 관련해서 사용되게 된 이유를 ibm 블로그 글에서 설명이 잘 되어 있음.

decltype()을 사용하여 a*b type으로 선언 하여 사용하고자 
왼쪽의 예를 오른쪽 처럼 바꿔서 사용할 경우 
컴파일러의 parsing 순서 상(왼쪽에서 오른쪽으로) a,b의 type을 확인하지 못해서 (a*b를 확인할 때 a,b가 선언되어 있지 않아서) 에러가 발생된다.

 template<class Tclass U>
U mul(T a, T b){
return a*b;
}
 template<class T>
decltype(a*b) mul(T a, T b){
return a*b;
}

그래서 이런 문제를 해결하고자 사용 하는 방법이 trailing(suffix) return type임.

 template<class T>
auto mul(T a, T b) -> decltype(a*b){
return a*b;
}

위의 feature들을 사용하면 아래와 같이 아무런 type을 지정하지 않고도 코딩이 된 예제가 나올 수 있음. 

 #include <iostream>
using namespace std;
template<typename T1, typename T2>
auto sum(T1 & t1, T2 & t2) -> decltype(t1 + t2){
return t1 + t2;
}
int main(){
auto i = 1;
auto j = 1.3;
auto k = sum(a,b);
cout << c << endl;
}



[참고]

- constexpr

 변수의 값이나 함수의 결과값을 constant 속성을 부여하여 compile 시에 사용될 수 있도록 함.
 아래 예제에서 보면 factorial, conststr, countlower함수 들이 constexpr로 선언되어 compile 시점에 out1, out2의 template인자로 사용되었음.
 #include <iostream>
#include <stdexcept>
 
// constexpr functions use recursion rather than iteration
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n-1));
}
 
// literal class
class conststr {
    const char * p;
    std::size_t sz;
 public:
    template<std::size_t N>
    constexpr conststr(const char(&a)[N]) : p(a), sz(N-1) {}
    // constexpr functions signal errors by throwing exceptions from operator ?:
    constexpr char operator[](std::size_t n) const {
        return n < sz ? p[n] : throw std::out_of_range("");
    }
    constexpr std::size_t size() const { return sz; }
};
 
constexpr std::size_t countlower(conststr s, std::size_t n = 0,
                                             std::size_t c = 0) {
    return n == s.size() ? c :
           s[n] >= 'a' && s[n] <= 'z' ? countlower(s, n+1, c+1) :
           countlower(s, n+1, c);
}
 
// output function that requires a compile-time constant, for testing
template<int n> struct constN {
    constN() { std::cout << n << '\n'; }
};
 
int main()
{
    std::cout << "4! = " ;
    constN<factorial(4)> out1; // computed at compile time
 
    volatile int k = 8; // disallow optimization using volatile
    std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
 
    std::cout << "Number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // implicitly converted to conststr
}

- decltype()
  초기값이 없어 type을 추론하지 못할 경우 변수나 표현식의 declared type을 사용하여 선언할 수 있게 해주는 keyword

 #include <iostream>
 
struct A {
   double x;
};
const A* a = new A();
 
decltype( a->x ) x3;       // type of x3 is double (declared type)
decltype((a->x)) x4 = x3;  // type of x4 is const double& (lvalue expression)
 
template <class T, class U>
auto add(T t, U u) -> decltype(t + u); // return type depends on template parameters
 
int main() 
{
    int i = 33;
    decltype(i) j = i*2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    auto f = [](int a, int b) -> int {
       return a*b;
    };
 
    decltype(f) f2{f}; // the type of a lambda function is unique and unnamed
    i = f(2, 2);
    j = f2(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}


- trailing(suffix) return type

간단히 말하면 함수의 뒷부분에 함수의 리턴 값의 type을 명시하는 것이다.(2)
일반적으로는 함수의 처음에 리턴값의 type을 명시하는 prefix return type을 사용함(1)

(2)예: (1) string to_string(int a);       (2) auto to_string(int a) -> string;

설명은 위를 참고하고 Ibm blog 글에서 아래와 같은 예제를 보여줌..
가독성 측면에서도 유용하게 사용될 수 있을 것으로 보임.
int형 템플릿 인자를 가지는 tmp class를 리턴하는 함수 포인터인 foo를 
trailing return type으로 표시할 경우 아래와 같다는..

 template <class T> class tmp{
public:
int i;
};
tmp<int> (*(*foo())())() {
return 0;
}
 template <class T> class tmp{
public:
int i;
};
auto foo(<wbr />)->a<wbr />uto(<wbr />*)()<wbr />->tm<wbr />p<in<wbr />t>(*<wbr />)()<wbr />{
return 0;
}

댓글 없음:

댓글 쓰기