Microsoft Melting Pot
C++ Korea 2nd Seminar
Ranges for The Cpp Standard Library
https://channel9.msdn.com/Events/Channel9-Korea/cplusplus/Ranges-for-The-C-Standard-Library
모던 C++의 시초인 C++11은 C++ 코드 전반에 많은 변화를 가져왔습니다. 그리고 최근 C++20의 표준위원회 회의가 마무리되었습니다. 내년에 C++20이 도입되면 C++11이 처음 도입되었을 때와 비슷한 규모, 또는 그 이상의 변화가 있을 것이라고 예상하고 있습니다. C++20에는 Concepts, Contract, Ranges, Coroutine, Module 등 굵직한 기능 외에도 많은 기능들이 추가될 예정입니다. 이번 세션에서는 C++20에 추가될 주요 기능들을 살펴보고자 합니다.
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
Build a full-functioned virtual machine from scratch, when Brainfuck is used. Basic concepts about interpreter, optimizations techniques, language specialization, and platform specific tweaks.
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
이 발표는 이전에 발표했던 내용에서 일부 사례 추가 및 최신 내용으로 갱신한 버전입니다.
모던 C++의 시초인 C++11은 C++ 코드 전반에 많은 변화를 가져왔습니다. 그리고 최근 C++20의 표준위원회 회의가 마무리되었습니다. 내년에 C++20이 도입되면 C++11이 처음 도입되었을 때와 비슷한 규모, 또는 그 이상의 변화가 있을 것이라고 예상하고 있습니다. C++20에는 Concepts, Contract, Ranges, Coroutine, Module 등 굵직한 기능 외에도 많은 기능들이 추가될 예정입니다. 이번 세션에서는 C++20에 추가될 주요 기능들을 살펴보고자 합니다.
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
Build a full-functioned virtual machine from scratch, when Brainfuck is used. Basic concepts about interpreter, optimizations techniques, language specialization, and platform specific tweaks.
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
이 발표는 이전에 발표했던 내용에서 일부 사례 추가 및 최신 내용으로 갱신한 버전입니다.
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
[C++ Korea 2nd Seminar] C++17 Key Features SummaryChris Ohk
C++은 10년 만에 C++11/14를 발표하면서 '모던 C++'이라는 이름으로 발전했습니다. 그만큼 새로운 기능들이 많이 추가되었습니다. 그리고 2017년, C++은 C++17이라는 이름으로 또 한 번의 발전을 준비하고 있습니다. 3년 주기로 빠르게 변화하는 모던 C++에 대비하기 위해, C++17에 추가될 주요 기능들을 살펴보고자 합니다.
[C++ Korea] Effective Modern C++ Study item14 16 +신촌Seok-joon Yun
[C++ Korea] Effective Modern C++ Study item14 16 +신촌
Item 14 : Declare functions noexcept if they won't emit exceptions. +윤석준
Item 15 : Use constexpr whenever possible. +이동우
Item 16 : Make const member functions thread safe. +제한재
http://github.com/ipkn/crow
Crow 프로젝트에서 사용한 C++11 기법들을 실제 구현에 대한 설명을 포함하여 자세히 설명한 발표자료입니다.
C++11 features used in Crow
video:
http://youtu.be/MixS9c3mE6U
https://vimeo.com/119627253
boost라이브러리 중에서 가장 많이 사용하는 기능인 BOOST_FOREACH()와 shared_ptr의 내부 구조를 분석합니다. 그리고 boost의 내부 구현에 사용된 이 기능을 프로그래밍에 응용하는 방법을 제시합니다.
* BOOST_FOREACH 구조 분석 및 응용
* shared_ptr 구조 분석 및 응용
[C++ Korea 2nd Seminar] Ranges for The Cpp Standard Library
1.
2. 최동민 / Nexon Korea
Ranges
for The C++ Standard Library
3. 발표 전…
• 다룰 것
• Ranges와 Ranges를 이루는 문법들
• 다루지 않을 것
• Concept
• 시간의 제약
• 코드 생략
• const, constexpr, explicit, overload, noexcept, namespace 등
• 가독성을 위해
9. 아직도 풀리지 않은 숙제
• 나는 컨테이너 전체를 소팅 하고 싶은데
왜 begin(), end() 요?
std::vector<int> numbers{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::sort(numbers.begin(), numbers.end());
10. 새로운 희망
• 오늘 소개할 Ranges 라이브러리 사용 시
• 모든 STL 알고리즘 함수에 대해
범위 기반 알고리즘 함수 제공
ranges::sort(numbers);
std::sort(numbers.begin(), numbers.end());
13. Ranges
• 범위를 다루는 Generic Library
• <Begin, End>
• 지원 컴파일러
• clang 3.4.0 이상
• gcc 4.9.0 이상
• 차기 C++ 표준 포함 예정
• https://github.com/ericniebler/range-v3
16. Ranges : 범위 기반 알고리즘 제공
• partition
auto it = ranges::partition(v, [](auto e){
return e % 2 == 0;
}
auto it = std::partition(v.begin(), v.end(), [](auto e){
return e % 2 == 0;
}
18. Ranges 를 왜 써야 하나요?
• 편의성(Convenience)
• 코드가 짧아져요.(!)
• 느긋한 계산(Lazy Evaluation)
• 무한 범위 표현 가능
• 불필요한 계산 회피
• 조합성(Composability)
• 함수형 프로그래밍에서 내세우는 그것
• 직관적인 표현 가능
• 코드 생산성 향상
22. Ranges : 컨테이너로의 전환
• [0, 10) 으로 채우기
• ranges::view::iota()
using namespace ranges;
vector<int> v = view::iota(0, 10);
23. Ranges : 컨테이너로의 전환
• ranges::view::iota()
vector range
(iota_view)
view::iota
User-Defined Conversion
24. User-Defined Conversion
• 분수 클래스 with Getter
struct Fraction{
int numerator;
int denominator;
double get(){
return static_cast<double>(numerator) / denominator;
}
};
int main(){
Fraction f{ 10, 20 };
cout << f.get() << endl; // 0.5
}
(http://en.cppreference.com/w/cpp/language/cast_operator)
25. User-Defined Conversion
• 마치 처음부터 double 이었던 것 처럼
(http://en.cppreference.com/w/cpp/language/cast_operator)
struct Fraction{
int numerator;
int denominator;
operator double(){
return static_cast<double>(numerator) / denominator;
}
};
int main(){
Fraction f{ 10, 20 };
cout << f << endl; // 0.5
}
26. Ranges : 기본 구조
• view::iota() 를 구현해 봅시다.
template <class T>
struct simple_iota_view{
T from_;
T to_;
operator vector<T>(){
vector<T> v;
v.reserve(to_ - from_);
for (; from_ != to_; ++from_)
v.emplace_back(from_);
return v;
}
};
27. Ranges : 기본 구조
• 비교
vector<int> v = simple_iota_view<int>{ 0, 10 };
현재 결과
우리가 원하는 결과
vector<int> v = view::iota(0, 10);
28. Ranges : 기본 구조
• Factory Functor
struct simple_iota_fn
{
template<class T>
simple_iota_view<T> operator()(T beg, T end)
{
return { beg, end };
}
};simple_iota;
29. Ranges : 기본 구조
• 최종 결과
vector<int> v = simple_iota(0, 10);
30. Ranges : 기본 구조
template <class T>
struct simple_iota_view
{
// ***
};
struct simple_iota_fn
{
// ***
} simple_iota; 호출할 Functor Instance
구현체 클래스
Functor 클래스
• 왜 이렇게 복잡하죠?
31. Ranges : 파이프라인
• Linux/Unix 파이프라인
• Vertical Bar ( | )
• Bar 왼쪽의 연산 결과를
오른쪽 연산의 입력으로 사용
http://www.guru99.com/linux-pipe-grep.html
grep(cat(“sample”), “Apple”)
현재 C++ 에서는?
32. Ranges : 파이프라인
• R 파이프라인
• %>% 를 통해 전달
http://www.r-statistics.com/2014/08/simpler-r-coding-with-pipes-the-present-and-future-of-the-magrittr-package/
33. Ranges : 파이프라인
• R 파이프라인
http://www.r-statistics.com/2014/08/simpler-r-coding-with-pipes-the-present-and-future-of-the-magrittr-package/
35. Ranges : 파이프라인
• Vertical Bar ( | ) 를 통해 전달
• 예시
• 0 ~ 4 를 반복하는 임의의 길이의 수열을 구해라
• [ 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 …. ]
vector<int> v =
view::iota(0, 5) |
view::cycle |
view::take(15);
[0, 1, 2, 3, 4] 수열을 만들고
그걸 무한히 반복하게 한 뒤
그 중 앞에서 15개(원하는 만큼)만 추린다.
36. Ranges : 파이프라인
• 비교 (0 ~ 4 를 반복하는 임의의 길이의 수열을 구해라)
for 루프
std::generate()
ranges
vector<int> v =
view::iota(0, 5) |
view::cycle |
view::take(15);
vector<int> v(15);
for (int i = 0; i < v.size(); ++i)
v[i] = i % 5;
int n = 0;
vector<int> v(15);
std::generate(v.begin(), v.end(), [&n] {
return n++ % 5;
});
37. Ranges : 파이프라인
• [0~15) 에서 5개씩 그룹 지어 각 그룹의 합을 구해라
• 0 + 1 + 2 + 3 + 4 = 10
• 5 + 6 + 7 + 8 + 9 = 35 …
vector<int> v =
view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
for (auto e : v)
cout << e << " ";
cout << endl;
// 10 35 60
auto quotient = [](int a, int b){
return a / 5 == b / 5;
};
auto sum = [](auto rng) {
return ranges::accumulate(rng, 0);
};
38. Ranges : 파이프라인
• [0~15) 에서 5개씩 그룹 지어 각 그룹의 합을 구해라
• 0 + 1 + 2 + 3 + 4 = 10
• 5 + 6 + 7 + 8 + 9 = 35 …
vector<int> v =
view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
for (auto e : v)
cout << e << " ";
cout << endl;
// 10 35 60
[[0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]]
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
[10,35,60]
39. Ranges : 파이프라인
• [0~15) 에서 5개씩 그룹 지어 각 그룹의 합을 구해라
• 0부터 5개씩 그룹 지어 각 그룹의 합을 6개 구해라?
vector<int> v =
view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
for (auto e : v)
cout << e << " ";
cout << endl;
// 10 35 60
?
40. Ranges : 파이프라인
• 조합성(Composability)
• [0~15) 에서 5개씩 그룹 지어 각 그룹의 합을 구해라
• 0부터 5개씩 그룹 지어 각 그룹의 합을 6개 구해라?
vector<int> v =
view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
for (auto e : v)
cout << e << " ";
cout << endl;
// 10 35 60
vector<int> v =
view::iota(0) |
view::group_by(quotient) |
view::transform(sum) |
view::take(6);
for (auto e : v)
cout << e << " ";
cout << endl;
// 10 35 60 85 110 135
41. Ranges : 출력
• 컨테이너 안에 무엇이 들었나
https://docs.python.org/3/tutorial/datastructures.html
vector<int> v = { ? };
42. Ranges : 출력
• 옆동네 파이썬에서는..
• 컨테이너 원소 쉽게 확인 가능
https://docs.python.org/3/tutorial/datastructures.html
43. Ranges : 출력
• 우리는..?
auto sq = [](int n) { return n * n; };
std::vector<int> v =
view::iota(0, 10) |
view::transform(sq);
for (auto e : v)
cout << e << " ";
cout << endl;
// 0 1 4 9 16 25 36 49 64 81
44. Ranges : 출력
• 이상과 현실
• 추가적인 코드가 불가피
cout << v << endl;
// 0 1 4 9 16 25 36 49 64 81
https://namu.wiki/w/%ED%98%84%EC%8B%A4%EC%9D%80%20%EC%8B%9C%EA%B6%81%EC%B0%BD
45. Ranges : 출력
• view 연산의 결과는 (대부분) 그대로 출력 가능
cout << view::iota(0, 10) << endl;
46. Ranges : 출력
• 파이프라인을 auto로 받아서 출력
• 컨테이너를 “범위”로 만들어 출력
auto rng = view::iota(0, 10) |
view::transform(sq);
cout << rng << endl;
cout << view::all(v) << endl;
47. Ranges : 출력
• 그룹 지어진 범위 출력
auto quotient = [](int a, int b) {
return a / 5 == b / 5;
};
auto rng = view::iota(0, 15) |
view::group_by(quotient);
cout << rng << endl;
// [[0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]]
48. Ranges : 출력
• Insertion Operator 오버로딩 *it 는 하위 range 일 수 있음
//struct view_interface
friend Stream &operator<<(Stream &sout, Derived &rng){
auto it = ranges::begin(rng);
auto const e = ranges::end(rng);
if(it == e)
return sout << "[]";
sout << '[' << *it;
while(++it != e)
sout << ',' << *it;
sout << ']';
return sout;
}
50. 의문의 rng
auto quotient = [](int a, int b) {
return a / 5 == b / 5;
};
auto rng = view::iota(0) |
view::group_by(quotient);
vector<int> v = rng | view::take(10);
cout << view::all(v) << endl;
52. Ranges : 타입
• 개념적 타입
• Range<T> 로 표현
• 중첩 가능
cppcon 2015 에릭 니블러
// Range<Range<int>>
auto rng = view::iota(0, 15) |
view::group_by(quotient);
// [[0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]]
53. Ranges : 타입
• 실제 타입
• take_exactly_view_<iota_view<int,void>,0>
auto rng = view::iota(0) |
view::take(10);
cout << typeid(rng).name() << endl;
//take_exactly_view_<iota_view<int,void>,0>
* 가독성을 위해 간략화함
54. Ranges : 기본 구조
• 생각해 봅시다
template <class T>
struct simple_iota_view;
simple_iota(0);
template <class T>
struct simple_take_view;
simple_take(10)
simple_take_view<simple_iota_view>
55. Ranges : 타입
• 표현식 템플릿 (expression template)
• 수식 전체가 하나의 타입
• 값이 실제로 필요할 때 계산
auto sum = v1 + v2 + v3;
//우항 : VecSum<VecSum<Vec, Vec>, Vec>
cout << sum[0] << endl;
struct VecSum {
double operator[](size_t i) {
return _u[i] + _v[i];
}
}
56. Ranges : 타입
• 표현식 별 타입
• iota | cycle | take
auto rng = view::iota(0, 5) |
view::cycle |
view::take(15);
cout << typeid(rng).name() << endl;
// take_exactly_view_<cycled_view<iota_view<int>>>
* 가독성을 위해 간략화함
57. Ranges : 타입
• 표현식 별 타입
• iota | group_by | transform
* 가독성을 위해 간략화함
auto rng = view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
cout << typeid(rng).name() << endl;
?
58. Ranges : 타입
• ?AU?$transform_view@U?$group_by_view@U?$tak
e_exactly_view_@U?...생략
이말년 시리즈
59. Ranges : 타입
• 대체 왜…?
• iota | cycle | take
• iota | group_by | transform
auto rng = view::iota(0, 5) |
view::cycle |
view::take(15);
// take_exactly_view_<cycled_view<iota_view<int>>>
auto rng = view::iota(0, 15) |
view::group_by(quotient) |
view::transform(sum);
// ?AU?$transform_view@U?$group_by_view@U?$take_exa
ctly_view_@U?...생략
60. Ranges : 타입
• Type Displayer
• 컴파일러 오류 메시지로 타입 확인
Effective Modern C++ : 항목 4
template <class T>
struct TD;
/***/
TD<decltype(rng)>();
61. Ranges : 타입
• Type Displayer 로 확인한 타입
• iota | group_by(람다) | transform(람다) * 가독성을 위해 간략화함
error : implicit instantiation of undefined template
'TD<transform_view<group_by_view<iota_view<int>,
(lambda at type_of_range.cpp:32:18)>,
(lambda at type_of_range.cpp:36:13)>>
62. Ranges : 타입
• 실제 타입은 매번 바뀌는데
어떻게 모두 Range<T> 처럼 사용할 수 있죠?
Effective Modern C++ : 항목 4
63. Ranges : 타입
• 개념적 타입 Range<T> 가 가능한 이유
• 공통 인터페이스 상속
• Range<T>는 일종의 인터페이스
• view_façade 를 상속받아
User-Define View 쉽게 정의 가능
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
iota_view
iota_view
iota_view
view_interface
view_facade
iota_view
65. Ranges : 자연수 표현
• 자연수
• [명사] <수학> 1부터 시작하여 하나씩 더하여 얻는 수를 통틀어 이르
는 말. 1, 2, 3 따위이다. (후략)
• 𝑥 ∈ ℕ
• 기존에 자연수를 표현하던 방법
네이버 국어사전
void foo(vector<int> v) {
for(auto i : v) {
/***/
}
}
// v는 자연수로 이루어진 벡터
assert(0 < i);
66. Ranges : 자연수 표현
• Ranges 자연수 표현
• 무한 수열 표현
• 반드시 view::take() 와 같은 한정 함수와 함께 사용
auto naturals = view::ints(1);
auto s = naturals | view::take(10);
cout << s << endl;
//[1,2,3,4,5,6,7,8,9,10]
67. Range Comprehension
• 수학 시간에 배운 것처럼 Range 만들기
• 지금까지 소개한 방식
// 집합 S = { 2 * x | x는 자연수 }
auto twice = [](int i) { return 2 * i; };
auto s = view::ints(1) | view::transform(twice);
68. Range Comprehension
• 수학 시간에 배운 것처럼 Range 만들기
• view::for_each 를 이용한 방식
// 집합 S = { 2 * x | x는 자연수 }
auto s = view::ints(1) | view::for_each([](int i){
return ranges::yield(i * 2);
});
69. Range Comprehension
• view::for_each 연산자 오버로딩
• view::for_each
• operator extraction equal (>>=) 오버로딩
auto s = view::ints(1) | view::for_each([](int i){
return ranges::yield(i * 2); //
});
auto s = view::ints(1) >>= [](int i) {
return ranges::yield(i * 2);
};
70. Range Comprehension
• 피타고라스 수
• 𝑎2 + 𝑏2 = 𝑐2 을 만족하는 세 자연수 쌍 𝑎, 𝑏, 𝑐 100개를 구하시오.
auto triples = view::ints(1) >>= [] (int z) { return
view::ints(1, z+1) >>= [=](int x) { return
view::ints(x, z+1) >>= [=](int y) { return
yield_if(x*x + y*y == z*z,
std::make_tuple(x, y, z));
};};};
http://ericniebler.com/2014/04/27/range-comprehensions/
72. Ranges : View / Action namespace
• ranges::view
• 데이터를 특정 시점으로 보기 위한 연산들
• 원본 데이터의 복사나 변환 X
• Lazy Evaluation 가능
• 표현식 템플릿
• ranges::action
• 데이터 원본을 직접 수정하는 연산들 (STL처럼)
auto sum = v1 + v2 + v3;
//우항 : VecSum<VecSum<Vec, Vec>, Vec>
http://www.chippostudy.com/subview/23255
74. Ranges : Action namespace
drop drop_while join split stable_sort
make_action remove_if shuffle slice sort
stride take take_while transform unique
75. Ranges : Action
• 컨테이너 직접 수정
std::default_random_engine gen;
std::vector<int> v = view::iota(0, 10);
action::shuffle(v, gen);
cout << view::all(v) << endl;
//[2,7,8,4,0,6,1,9,3,5]
v |= action::shuffle(gen);
76. Ranges : Action
• 복사
• 이동
auto v2 = v | ranges::copy | action::sort;
cout << view::all(v2) << endl;
//[0,1,2,3,4,5,6,7,8,9]
auto v2 = v | ranges::move | action::sort;
cout << view::all(v2) << endl;
//[0,1,2,3,4,5,6,7,8,9]
77. Ranges : Action
• View 와 함께 파이프라이닝
• 특정한 순회 방식에 알고리즘 적용
• 추가 컨테이너 필요 X
// 짝수만 셔플
v = view::ints(0, 10);
v | view::stride(2) | action::shuffle(gen);
cout << view::all(v) << endl;
//[6,1,8,3,2,5,0,7,4,9]
82. Reference
• 라이브러리 : https://github.com/ericniebler/range-v3
• 매뉴얼 : https://ericniebler.github.io/range-v3/
• 기타 참조
• http://ericniebler.com/
• Effective Modern C++
• http://stackoverflow.com/search?q=range-v3
• https://en.wikipedia.org/
• http://www.r-statistics.com/
• http://en.cppreference.com/
Editor's Notes
그럼 발표 시작하겠습니다.
차기 C++ 표준으로 제안된 Ranges 라이브러리에 대해서 발표할
최동민입니다.
먼저 발표 내용에 대해서 간략하게 설명을 잠깐 드릴게요.
이번 발표에서는 Ranges 라이브러리가 어떤 것인가, 와 Ranges를 이루는 문법들에 대한 내용을 다룰 예정이구요,
안타깝게도 Ranges만 하기에도 시간이 모잘라서
Ranges가 기반하고 있는 Concept 에 대해서는 다루지 않습니다.
그리고 발표 중간 중간에 나오는 예제 코드들은 가독성을 위해 여러가지 키워드들이 생략되어 있으니
보기 불편하시더라도 양해 부탁드립니다.
자 그럼 발표를 시작해 볼게요.
짧은 이야기를 하나 들려드릴게요.
태초에 이터레이터가 있었습니다.
그런데 이터레이터는 타입 이름이 너무 길었어요.
그래서 포문을 한줄에 쓸 수가 없었습니다.
매우 불편했습니다.
그러자 그 모습을 본 C++표준 위원회에서는
“내 너희에게 auto와 range-based for를 주노라” 하셨으니
코드가 매우 짧아졌고
C++프로그래머들이 보시기에 매우 좋았습니다.
이야기는 여기서 끝나지만,
현실에서는 또 다른 문제가 남아있습니다.
혹시 이런 의문을 가져본 적 있나요?
stl 알고리즘 함수를 쓸 때, 십중팔구는 begin 이랑 end를 넣을텐데
왜 컨테이너를 직접 못넘길까,
이런 생각, 저만 가졌었나요?
auto와 범위기반 포문이 들어왔지만
아직도 불편함의 씨앗이 남아있습니다.
이 이야기가 시사하는 점이 뭘까요?
이 이야기가 시사하는 점은, 나온지 30년이 넘은 C++이
아직도 발전할 여지가 있고, 실제로도 계속 발전하고 있다는 점입니다.
오늘 소개해드릴 Ranges 라이브러리를 사용하면
이와 같이 지긋지긋한 begin과 end를 없앨 수 있습니다.
그럼 ranges 라이브러리가 무엇인지 본격적으로 알아볼까요?
range라고 하면 아마 파이썬을 떠올리는 분들이 있을텐데요
이거랑은 조금 많이 다릅니다.
ranges는 이름에서 알 수 있듯이, 범위를 다루는 제네릭 라이브러리 구요
범위를 <Begin, End> 페어로 이루어진 객체로 다루면서
여러가지 편리하고 신기한 기능들을 제공합니다.
현재 제안된 라이브러리의 소스 코드는 깃허브에 올라와 있구요,
이 소스를 지원되는 컴파일러에서 컴파일하면 지금도 라이브러리를 사용해볼 수 있습니다.
이 라이브러리는 차기 c++표준으로 제안된 상태구요,
다음달에 있을 표준 위원회 회의에서 들어갈지 안들어갈지가 결정될 예정입니다.
그런데 보시면 지원 컴파일러가 clang 이랑 gcc밖에 없는데요
그럼 비주얼 스튜디오로는 못하느냐, 그건 아닙니다.
VS2015에서 프로젝트를 하나 여시고,
프로젝트 속성에 가시면 플랫폼 도구 집합이라는 옵션이 있잖아요?
이걸 Clang 3.7 with 마이크로소프트 코드젠으로 변경하시면
VS에서 Clang 컴파일러를 사용하실 수 있습니다.
만약 Clang 3.7 항목이 안보이면,
도구-> 확장 및 업데이트로 가셔서 저걸 깔아주시면 됩니다.
ranges는 아까 보여드렸던 것처럼
범위 기반 알고리즘 함수를 제공하는데요,
이게 다일까요?
당연히 (s) 아니겠죠?
이 라이브러리를 만든 에릭 니블러 씨는 ranges의 장점을 이렇게 서술하고 있습니다.
첫번째는 편의성입니다. 일단 코드가 더 짧아져요. 굉장한 장점이죠?
둘째는 느긋한 계산이 가능해집니다. 느긋한 계산이 뭐냐면, 어떤 연산이 있을 때,
아 이건 언젠간 해야되겠구나 기억만 하고 있다가, 나중에 사용자가 값을 달라고 요청을 할때
그 때에서야 부랴 부랴 연산을 수행하는 것을 말합니다. 사람으로 치면 참 안좋은건데,
프로그래밍에 이 개념이 도입되면, 무한한 범위를 표현할 수 있다던지, 실제로는 안해도 되는 계산들을 회피할 수 있다던지 하는
장점을 얻을 수 있습니다.
그리고 세번째는 조합성인데요, 이건 뭐냐면 코드를 마치 레고 블록으로 짜듯이 조립할 수 있는 성질을 말합니다.
언어에 조합성이 있으면 더 직관적인 표현이 가능해지고, 그로 인해 코드생산성이 향상되는 이점을 얻을 수가 있습니다.
대체 어떤 기능이 있길래 이런 장점들이 있다는 건지,
지금부터 알아보겠습니다.
여러분은 컨테이너를 0 이상 10 미만으로 채워넣을 때 어떤 방법을 사용을 하시나요?
저는 2009년에 프로그래밍을 처음 배웠는데요,
이 때 저는 배열을 선언을 해서, 원소를 0부터 9까지 일일이 적어넣는 식으로 배열을 초기화를 했었지요.
그 이듬해인 2010년에는 벡터라는 걸 알게되어서
와 이렇게 좋은게 있었구나 하면서
사이즈가 10인 벡터를 선언해서 각각의 원소를 for문을 통해 초기화를 하는 방식을 사용했었습니다.
이랬던 제가, 2016년 현재는
이렇게 벡터를 초기화 하고 있습니다.
역시 이니셜라이저 리스트가 최고시다 하면서
사실은 이것보다 조금 더 고오오오급진 방법이 있는데요,
바로, numeric 헤더에 있는 iota() 라는 함수를 사용하는 것입니다.
이함수는 begin() 부터 end()를 0으로부터 ++한 값으로 채워넣는 함수구요,
ranges 라이브러리 내에 정의된 범위 기반 알고리즘을 사용을 하면, begin 과 end를 빼버리고 v만 넣어서 사용할 수도 있습니다.
그런데 보시면, v는 선언과 초기화가 분리되어 있는 걸 보실 수가 있는데요,
별도의 함수를 만들지 않는 이상, C++에서 권장하는 선언과 동시에 초기화를 할 수가 없다는 단점이 있습니다.
그런데, ranges 라이브리는 이것에 대한 해답을 제공해주고 있는데요,
바로 view 네임스페이스 안에 있는 iota() 함수를 호출함으로써
선언과 동시에 초기화가 가능해집니다.
무슨 의미인지 아시겠죠? 0부터 ++해서 10 미만까지로 v를 초기화해라. 라는 뜻입니다.
그런데 이 iota() 리턴하는 타입은 사실 벡터가 아니에요.
사실, 범위를 뜻하는 객체가 리턴이 됩니다.
그럼 어떻게 이 범위 객체가 vector로 변환이 되는 걸까요?
그 원리는
바로 User-Defined Conversion 을 사용하는 것입니다.
User Defined Conversion 을 모르시는 분들을 위해 간단히 설명을 드릴게요
이렇게 분수 클래스가 있으면, 분자랑 분모를 뜻하는 멤버변수가 있겠죠?
그런데 이 객체가 가지는 값을 부동 소수점 형태로 출력을 할려면 분자 나누기 분모한 값을 리턴을 하는
getter를 만들 수 밖에 없습니다.
사용시에도 이렇게 f.get() 이라고 함수를 호출해줘야 하는데요,
이 객체를 진짜 double 처럼 사용하고 싶으면 이렇게 해주면 됩니다.
이렇게 operator double을 선언해주면, 이 객체 f가 double 이 사용될 수 있는 곳에서
double 로 암시적 형변환이 일어나게 됩니다.
물론 형변환 후 리턴되는 값은 operator double 함수가 리턴하는 값이겠죠?
이것을 이용하면, 아까 보셨던 범위 타입에서 벡터T로의 암시적 변환이 가능해집니다.
ranges라이브러리 구조도 파악할 겸 간단하게 한번 구현을 해볼게요.
iota함수는 시작값과 끝값을 넣었었죠? 그 두 값을 저장할 멤버변수를 만들구요,
방금 보셨던 User-defined conversio을 이용해서
벡터T로의 암시적 변환을 구현해줍니다.
내용은 간단해요.
암시적 변환이 일어날 때, 벡터를 하나 선언을 해서,
from 부터 to까지로 채운 뒤
v를 리턴하면 됩니다.
이렇게 하면 현재 보시는 것과 같은 결과를 얻으실 수가 있습니다.
그리고 우리가 원하는 최종 결과는 아래와 같은데요.
이 두 개가 같은 형태인가요?
아니죠?
뭔가가 좀 다릅니다.
보시면 이 심플 아이오타 뷰에는 템플릿 파라미터를 넣어줘야 해요.
자 여기서 퀴즈, 아래 표현식처럼 벡터를 초기화하려면, 이제 우린 무얼 해주어야 할까요.
그렇습니다. 이 simple_iota_view를 리턴하는 함수를 만들어 줘야 합니다.
그런데 그 함수를 그냥 제네릭 템플릿 함수로 만들면,
함수의 주소값을 취해야하는 상황에서는 아무리 짧은 함수더라도 인라인이 안되죠?
때문에 팩토리 함수 대신, 팩토리 펑터를 만듭니다.
이 펑터의 함수 호출 오퍼레이터를 템플릿으로 만들면, 인라인이 가능한 템플릿 함수가 되는거죠?
그래서 이 펑터는, 파라미터로 beg, end를 받아서, 이니셜라이저 리스트로 만들어서,
simple_iota_view를 초기화 하여 리턴하게 됩니다.
그런데 이 simple_iota_fn 이라는 클래스는, 클래스기 때문에 호출이 불가능하죠?
때문에 이 클래스의 인스턴스를 이렇게 전역 변수로 선언해주게 됩니다.
이것이 ranges라이브러리 내의 거의 모든 함수들의 구조이구요,
이 구조를 따르면, 다음과 같이
또 각 클래스마다 조금씩 차이가 있지만,
기본 구조는 이렇게 구현체 클래스 하나 당 펑터 하나씩이 대응된다는 점입니다.
벡터를 함수호출 하나만으로 선언과 동시에 초기화를 할 수가 있습니다.
이 구조가 단지 벡터 초기화 하려고 이렇게 만든 건 아니겠죠?
굳이 이렇게 복잡한 과정을 거쳐서 벡터를 초기화 할 필요가 없어요.
이 구조는 ranges가 제공하는 여러가지 기능들을 위해 고안된 구조인데요,
크게 세 개의 구성요소로 이루어져 있는 것을 보실 수 있습니다.
functor instance, functor class, 그리고 구현체 클래스로 이루어져 있는데요,
보시면 이 functor instance 뒤에 _fn 을 붙이면, functor 클래스가 나오고
뒤에 _view를 붙이면 구현체 클래스가 나오게 됩니다.
나중에 혹시 라이브러리 분석하실 일이 있으시면, 이렇게 접미어를 붙어서 검색하시면 되니 참고해주세요.
다음으로 설명드릴 것은 파이프라인 입니다.
내가 아는 그 파이프라인인가 하시는 분들도 계실 텐데요.
여러분께서 생각하시는 그 파이프라인이 맞습니다.
파이프라인은 이미 여러 곳에서 사용하고 있는 개념인데요
대표적으로 리눅스/유닉스의 파이프라인이 있지요.
리눅스 파이프라인은 바를 구분자로 삼아서,
바 왼쪽에 연산의 결과가 오른쪽 연산의 입력으로 사용되는 것을 말합니다.
오른쪽 예시를 보시면 맨 위에 cat이라는 명령어를 보실수가 있는데요,
이건 샘플이라는 파일의 내용을 문자열로 출력해라 라는 명령입니다.
그리고 그 아래 grep 이라는 명령어는 입력된 문자열 중에서 특정 패턴을 찾는 명령어입니다.
이 두개를 바로 이으면, cat으로 출력한 문자열을 grep의 입력으로 사용할 수 있는거죠.
이렇게 파이프라인으로 된 명령어들은 순서대로 읽을 수가 있습니다.
샘플의 내용 중에서 애플이란 단어를 찾아라.
이걸 기존의 C++로 표현하면 이렇게 되겠죠?
파이프라인은 Linux에만 있는 건 아닙니다.
R언어의 예를 한번 더 들어볼게요.
이것도 그대로 한번 읽어보겠습니다.
옥션 데이터를
타입별로 그룹지어서
입찰금의 총 합을 구해서
퀵플롯, 이건 그래프를 만들어주는 함수에요. 그래프를 만들어서
그려라
라고 읽을 수가 있구요
이 연산의 결과는
이렇게 멋진 그래프가 됩니다.
연산들을 연결하는게 마치 블록을 쌓는 것 같죠?
이것이 바로 조합성입니다.
이제 파이프라인에 대해 어느정도 감을 잡으셨을 텐데요
C++에서 제공하는 파이프라인을 보여드리겠습니다.
바로
이것입니다.
왠지 C++같지 않죠?
ranges에서 제공하는 파이프라인은 리눅스처럼 버티컬바를 사용해서,
왼쪽에서 오른쪽으로 각 연산의 결과물을 전달합니다.
이 예시는 0, 1, 2, 3, 4, 0, 1, 2, 3, 4를 반복하는 수열을 구하는 예제 이구요.
이것도 그대로 한번 읽어보죠
iota는 이미 보셨죠? 0 이상, 5미만의 수열을 만들고,
그걸 무한이 반복하게 한 뒤
그 중 앞에서 15개만 추려라
이런 뜻입니다.
기존 C++에서 사용하던 방식과 한번 비교해볼까요?
for루프를 사용한 방식과 std::generate()를 사용한 방식과 한번 비교해 보겠습니다.
여기서 주의 깊게 보셔야 하는 점은, 코드가 예쁜 게 아니라요.
차이점 1. 컨테이너 이름의 개수
차이점 2. 사람의 의식의 흐름과 비슷한
// for루프를 이용한 방식
// 컨테이너를 원하는 크기로 만들고, 반복문을 이용해 내용을 직접 채워넣는다.
// std::generate() 를 이용한 방식
// 컨테이너를 원하는 크기로 만들고, 내용을 채워넣을 방법을 지정한다.
// ranges 를 이용한 방식
// 원하는 수열을 만들고, 그 수열로 컨테이너를 초기화한다.
그리고 컨테이너 이름 개수도 비교
파이프라인 예시를 하나만 더 들어보죠.
0이상 15미만의 범위에서, 숫자를 5개씩 그룹지어서
각 그룹의 합을 구하는 예제입니다.
코드를 보시면, 제가 방금 말씀드렸던 그대로죠.
만약 요구사항이 이와 같이 바뀌었다고 생각해보죠. 기존에는 입력값의 범위가 주어졌었는데,
이제는 출력값의 개수만 주고 알아서 구하라고 하네요.
다행히 저희는 기존에 숫자가 5개씩 그룹을 지어 합을 구한다는 것을 알고 있으므로,
그냥 입력값을 15에서 30으로 증가시키면 됩니다.
그런데 이 코드를 처음 보는 사람이라면, 숫자가 몇 개씩 그룹을 짓는지 무엇을 보고 알 수 있을까요?
아마 group_by 와 transform 함수들을 찬찬히 살펴보아야 의도를 파악할 수 있을 것입니다.
ranges는 이런 요구사항의 변화가 있을 때 그렇게 해결하지 않죠.
ranges 를 이용하면 이렇게 수정이 가능합니다.
첫번째 빨간 네모를 보시면, iota의 인자가 하나인 것을 보실 수가 있습니다.
이렇게 iota 의 인자가 하나이면, 인자로부터 ++를 무한히 하는 범위를 표현하게 됩니다. 지금 보시는 네모는 0부터 시작해서, ++ 하여 얻을 수 있는 무한한 수열을 의미합니다.
이것에 대해서는 조금 있다가 다시 설명 드리겠습니다.
이 오른쪽 파이프라인들의 의미를 그대로 읽자면
“0부터 시작하는 무한한 수열을, 5로 나눈 몫으로 그룹지어서, 그 그룹의 합을 구한 것을, 6개 가져와라“ 라고 읽을 수 있습니다.
여기서 중요한 점은, 구하고자 하는 값에 입력 범위가 종속되지 않았다는 점입니다.
대신, 구하고자 하는 값을 직접 넣어 원하는 결과를 얻을 수 있습니다.
왼쪽 코드와 비교해보면, 하나의 코드 블록을 더 얹은 것으로 원하는 결과를 얻을 수 있었다는 걸 보실 수 있습니다.
이것이 바로 차기 C++ 표준에서 보실 수 있는, ranges의 파이프라인을 통한 조합성 입니다.
다음은 컨테이너 출력에 대해서 다시 한번 생각해보는 시간을 가져볼까 합니다.
코드 작성 도중 특정 런타임 시점에 컨테이너 안 원소를 확인하고 싶을 때?
디버거를 붙인다 --;
컨테이너 안의 내용물을 출력한다.
이게 바로, 현시창이죠.
적어도 지금까지는 그래왔습니다.
보시면서 계속 이 auto로 받은 알엔지 라는 객체가 대체 무슨 타입일까
뭐길래 파이프라인이나 출력 같은 게 가능할까
대체 타입이 뭘까요?
먼저 개념적 타입부터 설명을 해 드릴게요.
ranges 의 view 네임스페이스 안에 있는 연산들은 모두 Range<T> 라는 타입을 리턴합니다.
그런데 이 Range<T>는 실제 타입은 아니구요, 그냥 “범위”라는 개념입니다.
한번 생각을 해 보죠. 왜 이렇게 되는지?
저희가 처음에 만들었던 simple_iota() 펑터를 떠올려 보겠습니다.
simple_iota()는 simple_iota_view<T>를 리턴했죠?
그럼 같은 방식으로 이런 클래스들도 만들었다고 생각해 보겠습니다.
simple_take()는 simple_take_view<T>를 리턴하겠죠?
그런데 만약에 simple_iota() 의 결과가 simple_take()의 입력으로 들어가게 된다면 어떻게 될까요?
simple_take_view의 T에는 simple_iota_view가 들어가게 됩니다.
이렇게 되면 무엇이 가능해지느냐, 보시면 simple_take_view와 simple_iota_view 의 조합으로
타입만으로 지금 무엇을 하려고 하는지를 알 수 있는데요.
이런것을
표현식 템플릿이라고 합니다.
좀 더 자세히 설명 드리자면,
수식 전체가 하나의 타입이 되는 것인데요,
이게 표현식 템플릿이 아니라 일반 컨테이터 벡터 타입이라고 생각해보겠습니다.
벡터는 벡턴데, 더하기 연산이 오버라이드 되어있어서
각각의 원소들, 0번원소끼리, 1번원소끼리, 2번 원소끼리 끼리끼리 더할 수 있고,
각 벡터의 사이즈는 모두 같다고 가정하겠습니다.
이 때, 이 결과물인 sum의 원소를 모두 접근하는게 아니라, 랜덤으로 몇 개만 접근한다고 해보죠.
그 몇 개를 위해서 v1 v2 v3의 모든 원소를 더하는 건 낭비죠.
이런 상황에 표현식 템플릿을 사용하면, 가독성을 trade off 하지 않고도 효율적으로 계산을 할 수가 있습니다.
바로, 참조 연산자를 오버라이드해서, 실제 원소를 참조할 때에만 더하기 연산을 하는 것입니다.
ranges::view 연산들은 이렇게 표현식 템플릿으로 구현되어있습니다.
각 파이프라인에 따른 실제 타입들을 한번
typeid로 찍어보겠습니다.
자, 이 표현식도 한번 예상해 볼까요?
당연히 각 연산의 구현체 클래스, transform_view, group_by_view, iota_view 로 구성된 클래스를 볼 수 있겠죠?
그래서 이 range를 typeid로 출력하면
이렇게 나옵니다.
오.. 뭐지?
아까는 예쁘게 나왔는데.
위에는 없고 아래는 있는게 무엇일까요?
선착순 한명 책 선물 드리겠습니다.
바로 람다입니다.
기억하실지 모르겠지만, 코션트랑 썸은 람다를 auto로 받은 것입니다.
이 람다들이 group_by에 템플릿 파라미터로 들어가면서,
타입이 굉장히 복잡해지게 되는데요,
clang typeid 에서는 이런 경우에 이렇게 출력 형식이 깨지는 경우가 생기더라구요?
이럴때에는
이펙티브 모던 C++ 항목 4에 설명된 TypeDisplayer 를 이용해서
컴파일 에러로 타입을 찍어보면
이렇게 예쁘게 타입이 나오는 것을 확인하실 수 있습니다.
심지어 람다가 선언된 파일이랑 몇번째 줄 몇번째 열에 선언 되었는지 까지 알 수 있습니다.
그럼 여기서 또하나의 의문점을 던져볼게요.
조합성은 코드를 조립할 수 있는 성질이라고 말씀드렸는데요,
어떤 함수들을 조합하느냐에 따라서
range 의 실제타입은 매번 바뀌는데
어떻게 Range<T> 라는 추상적 타입을 계속 유지할 수 있을까요?
그 해답은 모든 구현체 클래스가, 공통된 인터페이스를 상속받기 때문입니다.
즉, range<T>는 일종의 인터페이스인 셈이죠.
마지막으로 ranges를 통해 가능해지는 표현식에 대해 알아보겠습니다.
먼저 자연수, 초등학생의 마음가짐으로 자연수의 정의에 대해서 다시 한번 생각해보겠습니다.
(정의를 읽고) (수학표현을 읽고) 이런식으로 표현을 하죠?
c++에서 기존에 자연수를 표현하던 방법은 이러했습니다.
주석으로 표시를 하거나,
아니면 코드에 어설트를 넣는거죠.
그래도 이 “무한한 범위“ 라는 정의를 충족시키지는 못하죠?
ranges는 앞서 보셨다시피, iota나 cycle 등의 view연산들을 통해서 무한한 범위를 표현할 수가 있습니다.
지금 보시는 ints 는 iota를 int 로 특수화 한, 그냥 특수화만 한 함수입니다. int 밖에 못넣어요.
이 ints에 1을 넣으면, 1부터 무한히 증가하는 정수를 표현하게 되는데요,
이게 바로 자연수죠? 1부터 1씩 더해서 얻을 수 있는 수들의 집합이 됩니다.
그럼 이 naturals를 cout으로 출력해버리면 어떻게 될까요?
무한루프를 돌게 되겠죠?
그러므로 이런 무한 수열은 반드시 take와 같은 한정함수와 함께 사용해야 합니다.
제가 마지막으로 소개해 드릴 것은 range 컴프리헨션인데요.
파이썬이나 하스켈의 list 컴프리헨션과 대응되는 개념입니다.
수학시간에 배웠던 것처럼 range를 한번 표현해볼게요.
집합 S는 2 * x 바 x는 자연수. 이러면
집합 S는 자연수의 각각의 원소를 2배 한 수들의 집합이 되는거죠?
이걸 지금까지 소개했던 방식을 사용하면, 위와 같이 표현을 할 수가 있습니다.
각각의 원소를 직접 transform 하는 방식이죠.
그런데 컴프리헨션이란 보통, 이미 존재하는 리스트에서 새로운 리스트를 만드는 것을 의미합니다.
위에건 새로운 리스트를 만든다기보단, 기존 리스트를 수정한다는 개념에 가깝죠?
이럴 때 사용하는 것이 view::for_each입니다.
view::for_each 는 입력으로 들어온 범위를 순회하면서 새로운 범위를 만드는 함수입니다.
ranges::yield 로 리턴된 값이 새로운 범위의 원소가 되게 됩니다.
이것을 range comprehension 이라고 부릅니다.
그리고 이 view::for_each 는 특별하기 때문에, 아래처럼 연산자 오버로딩을 해두었는데요
이 두 코드는 같은 동작을 하는 코드입니다.
이 range comprehension 을 이용하면 무엇을 할 수 있냐면,
파이썬이랑 똑같아요, 기존에 존재하는 범위로부터, 새로운 범위를 쉽게 만들수가 있습니다.
이 예제는 피타고라스 수를 구하는 예제인데요,
아까 yield()로 리턴한 값들이 새로운 범위의 원소가 된다고 말씀 드렸었죠?
yield_if() 는 이 앞에 있는 표현식이 참이면, 뒤에 있는 값을 새로운 범위에 추가하고
앞에 표현식이 거짓이면 그냥 건너뛰는 동작을 합니다.
이렇게 만들어진 triples 객체에
take 로 몇 개를 가져올지만 지정해주면
원하는 개수의 피타고라스 수를 구할 수가 있습니다.
이것을 삼중 for문을 이용해서 구현하는 과정을 생각해보면,
이 표현식이 얼마나 간단한지 어렴풋이 느끼실 것입니다.
에릭 니블러씨는 이 예제를 구현하고
Haskell and Python have list comprehensions. C# has LINQ and yield. And now C++ has range comprehensions.
하스켈과 파이썬은 리스트 컴프리헨션이 있고, C#은 링큐와 yield가 있다. 그리고 지금, C++에는 레인지 컴프리헨션이 있다. 라고 말했었습니다.
얼마나 유용한지는 여러분의 판단에 맡기겠습니다.
지금까지 저는 ranges 라이브러리 내에 있는 View namespace 에 대해서만 설명을 드렸었는데요
사실 중요한 namespace 가 하나 더 있습니다.
바로 Action 입니다.
view namespace 는 데이터를 특정 --- 연산들이 들어있습니다.
시점만 변경하는 거기 때문에, 데이터 원본을 직접 수정하거나, 복사하지 않는 것이 특징입니다.
주로 컨테이너 순회 방법을 바꾸거나, 각 원소를 조금 다른 방식으로 읽는다거나 하는 연산들이 이 namespace 에 들어있습니다.
그리고 아까 설명드렸던 것처럼, 표현식 템플릿을 통해서 느긋한 계산이 가능합니다.
반면에 action namespace 에는 데이터 원본을 직접 수정하는 연산들이 들어있습니다.
이건 view namespace 의 연산들이구요.
많죠?
이건 action namespace 의 연산들입니다.
그냥 나중에 참고하시라고 넣었습니다.
action에 대해서 좀더 설명드리면,
이렇게 stl 처럼 v의 원소를 직접 셔플한다거나 할 때 사용하는 함수들의 집합입니다.
그런데 stl과 다른점은, 이렇게 파이프라이닝이 가능하다는 점입니다.
데이터 원본을 직접 수정할 수 있으니까, 복사나 이동에 민감할 수 밖에 없는데요,
파이프라인을 통해서도 복사나 이동을 할 수 있구요
action의 진면목은, view와 함께 사용했을 때 나오죠.
view는 특정 데이터를 순회하는 방법을 바꿀 수 있다고 말씀드렸었는데요, 기존의 데이터에 대한 특정 순회방식을 하나의 “범위“ 로 인식하기 때문에
마치 이 범위가 하나의 컨테이너인 것 처럼 action함수를 사용할 수 있습니다.
이 예제를 보시면 v를 0에서 10으로 초기화를 하고 있는데요
이 v를 , stride, 성큼성큼 걷다 라는 뜻인데요, 이렇게 쓰면 2개씩 건너뛰면서 순회하게 됩니다. 0, 2, 4, 8 이런 식으로 순회하겠죠?
이 0, 2, 4, 8을 하나의 범위로 인식해서 액션의 셔플을 돌리면
이렇게 짝수만 셔플된 컨테이너 v를 얻을 수가 있습니다.
기존에 이런 구현을 하기 위해서는 추가적인 컨테이너가 꼭 필요했는데요,
range를 사용하면 훨씬 더 간편하게 구현할 수 있습니다.
드디어 서머리네요
Range를 사용함으로써의 장점은 명확합니다.
범위 기반 알고리즘이나 파이프라인, 느긋한 계산, range 컴프리헨션 등을 이용한
간결하고 강력한 표현이 가능해집니다.
또 표준 컨…. ----
반면, 단점은
view같은 경우 표현식 템플릿은 결국 컴파일 타임에 만들어지기 때문에
상대적으로 긴 컴파일 타임을 가질 수 밖에 없습니다.이는 현재 개선해야 할 사항으로 작업큐에 올라가 있는 것으로 알고 있습니다.
마지막으로 표준화 작업으로
지금도 고생중이실 에릭 니블러씨께 감사하다는 말씀을 전하면서
이 발표를 마치겠습니다.
감사합니다.