앞의 vector 출력문을 함수로 구현해 보자.


void print( const std::vector< int >& some ) // '&' 참조기호를 사용한 이유는 스택에 복사하기 싫기 때문

{

    for( auto one : some )

        std::cout << one << std::endl;

}


간단하지만, int 타입의 vector 밖에 사용할 수 없으니 답답하다.

템플릿으로 일반화 하자.


template< class T >

void print( const std::vector< T >& some )

{

    for( auto one : some )

        std::cout << one << std::endl;

}


훌륭하다.

하지만 vector 뿐만 아니라 다른 컨테이너들의 경우는 일일이 만들어주어야 한다.


template< class T >

void print( const T& some )

{

    for( auto one : some )

        std::cout << one << std::endl;

}


더 간단하고 더 효과적으로 일반화가 되었다.


당연하게도 모든 컨테이너가 ranged for 나, std::cout 으로의 즉시 출력을 지원하지 않으니 만능은 아닐 것이다.

ranged_for 를 지원하기 위해서는 begin(), end() 를 비롯한 몇 가지 인터페이스를 구현해 주어야 한다.

사용자 클래스들인 경우는 std::cout 으로의 출력을 지원하는 인터페이스도 만들어줘야 한다.


여기서 더 일반화 할 꺼리가 무엇이 있을까?

지금은 std::cout 으로의 콘솔 출력만 지원하는 셈이다.

일반적인 output stream 에 모두 적용할 수 있도록 하고싶다면,


template< class T >

std::ostream& print( std::ostream& os, const T& some )

{

    for( auto one : some )

        os << one << std::endl;

    return  os;

}


처럼 구현하고 print( cout, some ); 처럼 호출해주면 된다.

return 이 등장한 이유는 print( print( print( .... 처럼 직렬 표기를 가능케 하기 때문.


template< class T >

auto& print( std::ostream& os, const T& some )

{

    for( auto one : some )

        os << one << std::endl;

    return  os;

}


요게 조금 더 깔끔하긴 하다.


급식시절 논리에 대해 조금이라도 귀담아 들었던 사람은

성급한 일반화의 오류를 기억할 것이다.


이걸 줄이고 일반화해,


template< class T1, class T2 >

auto& print( T1& os, const T& some )

{

    for( auto one : some )

        os << one << std::endl;

    return  os;

}


이렇게 한다면, 당신의 print 함수는 print( int, int ) 같은 기능을 추가 지원할 수 없게 된다.

아이러니하게도 약간은 구체성과 제약을 갖는게

정의간의 간섭을 줄일 수 있다는 것이다.


여기다 연산자 오버로딩( 실은 오버라이딩 ) 을 끼얹으면,


template< class T >

auto& operator+=( std::ostream& os, const T& some )

{

    for( auto one : some )

        os << one << std::endl;

    return  os;

}


이런 표현을 쓸 수 있다. 눈치 빠른 사람은 알겠지만 operator+= 까지가 이름이 되었고,

사용할때만 += 이란 이름으로 쓸 수 있는 함수라고 생각하면 된다.

std::cout += some; 처럼 사용하면 된다.

다만, 이경우 std::cout 에 원래 정의되어 있던 << 연산자와 우선순위가 달라지기 때문에,

연속된 출력 연산의 경우

순차가 될 수 있도록 연산자 우선순위를 고려해 괄호를 어딘가 싸줘야 할 것이다.


연산자는 함수보다 더 일반화 된 형태기 때문에,

논리적 충돌 가능성은 더 높다고 하겠다.

알고 쓰면 좋지만, 정말 충돌이 없는 경우 맛깔스럽게 쓰는 정도가 정신건강에 이롭고,

기존에 정의된 연산자 오버로딩이 있다면 가급적 사용하지 않는게 좋다.


이제 한 가지 variation 이 남은것 같다.


지금의 코드는 항목 하나 단위로 개행을 실시하고 있다.

옆으로 나란히 붙여서 찍고 싶으면 어떻게 해야할까? ( 0 1 2 3 4 5... 처럼 )


template< class T >

auto& operator+=( std::ostream& os, const T& some )

{

    for( auto one : some )

        os << one << " ";

    return  os;

}


처럼 바꾸면 간단하지만, 다 출력한 후 마지막 스페이스가 거슬릴 경우가 있다.

[ 0 1 2 3 4 5 ] 처럼 브라켓 '[' 와 멤버 사이에 한 칸을 띄우면 문제는 심플해진다.


template< class T >

auto& operator+=( std::ostream& os, const T& some )

{

    os << "[ "; // 미리 한칸 띄었으니 제일 마지막 띄움이 등장해도 대칭이다

    for( auto one : some )

        os << one << " ";

    os << "]";

    return  os;

}


하면 되잖아?


그런데 아무래도 뒤의 공백을 없애고싶다면 ranged_for 를 포기하거나

( 앞 강의의 iterator 참고, 마지막 한 개만 따로 찍기 )

꼼수로 출력한 스페이스를 지우는 방법이 있겠다. ( 변태스럽다. 근데 답은 맞다 )


template< class T >

auto& operator+=( std::ostream& os, const T& some )

{

    os << '[';

    for( auto one : some )

        os << one << " ";

    os << "]"; // 백슬래시 b 를 앞에 넣었다. 글에 안나올 뿐.

    return  os;

}


어느쪽을 선호하든 여러분의 선택.

여기도 일반화와 특수화의 개념이 포함되었다 하겠다.


그런데 기왕 vector 가 나온김에 한 가지는 짚고 가자.

vector< type > 에서, type 에 bool 이 해당하는 경우는, 다른 type 과 달리 특수한 경우다.

보통 bool 은 1바이트 단위 자료형으로 선언이 되어 있지만,

vector< bool > 은 메모리 공간 최적화를 위해 비트 단위로 저장된다.

따라서 개수 단위로 위치가 지정되지 않기 때문에 begin(), end() 는 있지만

data() 인터페이스 자체가 없다.

( 포인터로 쓰지마~ 우리 컨셉이랑 달라~ 란 뜻 )


다음 시간엔 템플릿으로 수열을 제너레이팅해 보겠다.