Post

(C++) 컨테이너 안에 클래스가 들어있을 때, 클래스 안의 멤버들을 순회하는 이터레이터를 반환받는 방법 + mem\_fn

vector<cls>가 있을 때, cls.value들만 뽑아내고 싶은 경우가 빈번하게 있다. 일반적인 경우 그냥 반복문 돌면서 처리해주면 되지만, 라이브러리 등을 사용할 때는 반드시 cls.value로 이루어진 iterator를 넘겨야만 하는 경우가 있다.

이런 경우 처음으로 드는 생각은 함수형의 filter()인데, 이는 copy가 발생하게 된다. copy 없이 cls 내의 멤버인 cls.value들만 순회하는 iterator는 다음과 같은 방식으로 만들 수 있다.

boost::transform_iterator

내부적으로 정확히 어떻게 동작하는건지는 찾아보지 않았지만, 첫 번째 인자로 지정된 iterable의 원소를 하나 씩 꺼내서 두 번째 인자로 지정된 function에 통과시킨 결과를 반환한다는 느낌이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
int x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
const int N = sizeof(x) / sizeof(int);

typedef boost::transform\_iterator<std::function<int(int)>, int\*> doubling\_iterator;


std::function<int(int)> doubling\_lambda = [](int x) { return x \* 2; };  // or just auto.
doubling\_iterator i(x, doubling\_lambda);
doubling\_iterator i\_end(x + N, doubling\_lambda);

std::cout << "multiplying the array by 2:" << std::endl;
while (i != i\_end)
std::cout << \*i++ << " ";
std::cout << std::endl;
}

따라서 클래스 내의 멤버에 접근하는 람다를 넘겨주면, 다음과 같이 iterator를 반환받을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void memberIterTest() {
vector<Foo> vec = { Foo(1), Foo(2), Foo(3), Foo(4) };

  


// 방법 1. 직접 선언해서 사용하는 방법

typedef boost::transform\_iterator <std::function<int(Foo)>, vector<Foo>::iterator> cls\_iter;

auto getData = [](Foo cls) { return cls.data; };

cls\_iter i(vec.begin(), getData);

cls\_iter i\_end(vec.end(), getData);

  


// 방법2. make\_transform\_iterator를 사용하는 방법


auto getData = [](Foo cls) { return cls.data; };

auto i = boost::make\_transform\_iterator(vec.begin(), getData);

auto i\_end = boost::make\_transform\_iterator(vec.end(), getData);




while (i != i\_end)

std::cout << \*i++ << " ";

std::cout << std::endl;
}

1
2
3
1 2 3 4

lambda를 사용할 수 없다면, std::mem_fn

std::mem\_fn은 클래스의 멤버를 가리키는 wrapper object를 반환하는 함수다. 메서드에 사용하면 함수 포인터를 반환하고, 멤버 변수에 사용하면 그 멤버 변수의 getter처럼 동작하는 함수 포인터를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <functional>

  

class Foo {
public:
int data = 7;
};

  

int main() {
Foo f;
auto getData = std::mem\_fn(&Foo::data);
std::cout << "data: " << getData(f) << '\n';
}

따라서 다음과 같이 람다를 대체해서 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void memberIterTest() {
vector<Foo> vec = { Foo(1), Foo(2), Foo(3), Foo(4) };

  


// 방법 1. 직접 선언해서 사용하는 방법

// mem\_fn같은 경우 타입으로 decltype을 이용하는 것이 더 나아보인다. std::function<int(Foo)>를 사용하는 것 보다.

typedef boost::transform\_iterator <decltype(std::mem\_fn(&Foo::data)), vector<Foo>::iterator> cls\_iter;

cls\_iter i(vec.begin(), std::mem\_fn(&Foo::data));

cls\_iter i\_end(vec.end(), std::mem\_fn(&Foo::data));

  


// 방법 2. make\_transform\_iterator를 사용하는 방법

auto i = boost::make\_transform\_iterator(vec.begin(), std::mem\_fn(&Foo::data));

auto i\_end = boost::make\_transform\_iterator(vec.end(), std::mem\_fn(&Foo::data));




while (i != i\_end)

std::cout << \*i++ << " ";

std::cout << std::endl;
}

This post is licensed under CC BY 4.0 by the author.