«   2024/04   »
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
Archives
Today
Total
04-20 10:05
관리 메뉴

lancelot.com

range library 본문

프로그래밍

range library

lancelot50 2022. 7. 30. 18:03
  • range_for 를 이용해서 화면에 출력
#include<vector>
#include<iostream>

int main()
{
	std::vector v = { 1,2,3,4,5 };

	for (auto e : v)
		std::cout << e << std::endl;
}
  • 컨테이너의 일부만 출력할 수 있을까?

range_for 의 원리

  • container의 일부만 가리키는 take_view class
    • 컨테이너(vector 등) 처럼 자원을 소유하지는 않음
    • 이미 존재하는 컨테이너에 대한 새로운 뷰를 제공

take_view

#include<vector>
#include<iostream>

template<typename T>
class take_view
{
	T& rng;
	std::size_t count;
public :
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};

int main()
{
	std::vector v = { 1,2,3,4,5 };

	for (auto e : v)
		std::cout << e << std::endl;

	for(auto e : take_view{v, 3})
		std::cout << e << std::endl;
}

 

  • ranges::ref_view

vector<int>&와 ranges::ref_view 비교

#include<vector>
#include<iostream>
#include<ranges>

int main()
{
	std::vector v1 = { 1,2,3 };
	std::vector v2 = { 5,6,7,8,9 };

//	std::vector<int>& r1=v1;
//  std::vector<int>& r2=v2;

	std::ranges::ref_view r1 = v1;
	std::ranges::ref_view r2 = v2;

	r1 = r2;

	std::cout << v1.size() << std::endl;// 3
	std::cout << v2.size() << std::endl;// 5
	std::cout << r1.size() << std::endl;// 5
	std::cout << r2.size() << std::endl;// 5
}

 

  • std::ranges::ref_view
    • 이동가능한 참조
    • 포인터를 사용한 간단한 유틸리티
    • C++11에 추가된 std::reference_wrapper 의 view 버전
  • std::reference_wrapper
    • C++ 에 추가된 이동가능한 참조.
    • 구현 원리는 내부 멤버 포인터 사용
    • 범용적인 용도
  • std::ranges::ref_view
    • reference_wrapper 의 컨테이너 버전
    • begin(), end(), size(), empty(), data(), base() 등의 멤버함수 제공
#include<vector>
#include<iostream>
#include<ranges>

int main()
{
	std::vector v1 = { 1,2,3 };

	std::ranges::ref_view r1 = v1;

	auto p1 = r1.begin();
	auto p2 = r1.end();

	std::cout << &v1 << std::endl;
	std::cout << &r1.base() << std::endl;
}

 

  • take_view 를 take_view 에 대입해보면, 참조가 가리키는 대상이 복제되어서 v1에 v2가 복제됨
#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view
{
	T& rng;
	std::size_t count;
public:
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }

	// 멤버에 &가 있으면( 여기서는 T& rng) operator= 를 제공해주지 않아서구현해야한다.
	take_view& operator=(const take_view& tv)
	{
		if (&tv == this)
			return *this;
		rng = tv.rng;
		count = tv.count;
		return *this;
	}
};

int main()
{
	std::vector v1 = { 1,2,3 };
	std::vector v2 = { 5,6,7,8,9 };

	take_view tv1(v1, 2);
	take_view tv2(v2, 3);

	tv1 = tv2;

	std::cout << v1.size() << std::endl;//5
	std::cout << v2.size() << std::endl;//5
}

참조하고 있는 원본이 복사된다

 

  • tv 뷰를 가리키는 포인터만 옮기고 싶다면, take_view에 ref_view를 사용한다

rev_view를 사용하면, tv1이 v2를 가리킨다

#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view
{
	std::ranges::ref_view<T> rng;
	std::size_t count;
public:
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};

int main()
{
	std::vector v1 = { 1,2,3 };
	std::vector v2 = { 5,6,7,8,9 };

	take_view tv1(v1, 2);
	take_view tv2(v2, 3);

	tv1 = tv2;

	std::cout << v1.size() << std::endl;// 3
	std::cout << v2.size() << std::endl;// 5
}

 

  • ref_view를 take_view와 reverse_view에 적용
#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view
{
	std::ranges::ref_view<T> rng;
	std::size_t count;
public :
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};

template<typename T>
class reverse_view
{
	std::ranges::ref_view<T> rng;
public:
	reverse_view(T& r) : rng(r) {};

	// ref_view 는 역반복자가 없기때문에 생성하는 make_reverse_iterator() 를 사용한다
	auto begin() { return std::make_reverse_iterator(rng.end()); }
	auto end() { return std::make_reverse_iterator(rng.begin()); }
};

int main()
{
	std::vector v = { 1,2,3 };

	for (auto e : reverse_view(v))
		std::cout << e << std::endl;
	
	std::ranges::ref_view r1(v);
//	std::ranges::ref_view r2(std::vector<int>{1,2,3}); // error - ref_view는 내부적으로 포인터기때문에 임시객체(rvalue)를 받을 수 없다
}

 

  • take_view(reverse_view) 중첩을 적용하면...
    • 아래 take_view tv(reverse_view(v), 2) 에서 빌드가 안되야하는데... 왜 되지? VS2022, C++20 인데..-_-;
    • 어쨌든 안되는 이유는 reverse_view(v)가 임시객체이기때문.

#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view
{
	std::ranges::ref_view<T> rng;
	std::size_t count;
public :
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};

template<typename T>
class reverse_view
{
	std::ranges::ref_view<T> rng;
public:
	reverse_view(T& r) : rng(r) {};

	auto begin() { return std::make_reverse_iterator(rng.end()); }
	auto end() { return std::make_reverse_iterator(rng.begin()); }
};

int main()
{
	std::vector v = { 1,2,3 };

	reverse_view rv(v);
//	take_view tv(rv, 2);

	take_view tv(reverse_view(v), 2);

	for (auto e : tv)	//3, 2
		std::cout << e << std::endl;

	take_view tv1(v, 3);	// ref_view<vector> 를 멤버로 포함
	take_view tv2(rv, 3);	// rev_view<reverse_view> 가 아니라
							// rv(reverse_view) 의 복사본을 멤버로 포함했으면 좋겠다
}
  • 그럼 임시객체로 하지말고, reverse_view(v) 처럼 들어오면, 복사본을 가지게 만들어보는 방법은?

이렇게 하면 어떨까?

 

  • all 함수를 통해, 전달받은 컨테이너의 상태를 확인하고 다르게 반환해준다.
#include<vector>
#include<iostream>
#include<ranges>

// all 함수 안에서 ref_view 인지 아닌지 구분해보자
//  1. view class는 std::ranges::view_base에서 상속받는다

template<typename T>
class take_view : public std::ranges::view_base
{
	std::ranges::ref_view<T> rng;
	std::size_t count;
public :
	take_view(T& r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};

template<typename T>
auto all(T&& arg)
{
					// T가 참조를 포함하고 있을수 있으니 참조를 제거한다
	if constexpr (std::ranges::view<std::remove_cvref_t<T>>)
	{
		std::cout << "view" << std::endl;
		//복사본 반환
		return std::remove_cvref_t<T>(arg);
	}
	else
	{
		std::cout << "not view" << std::endl;
		// vector => rev_view<vector>
		return std::ranges::ref_view<std::remove_cvref_t<T>>(arg);
	}
}

int main()
{
	std::vector v = { 1,2,3 };

	take_view tv(v, 2);

	auto a1 = all(v);
	auto a2 = all(tv);

	std::cout << typeid(a1).name() << std::endl;
	std::cout << typeid(a2).name() << std::endl;
}

 

  • all이 표준에도 있으니, 이것을 take_view에 적용해보자
  • rng 의 타입을 미리 정해두지말고, C++17부터 들어간 template deduction guide를 사용해서 정한다
#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view : public std::ranges::view_base
{
	T rng;
	std::size_t count;
public :
	take_view(T r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};
// C++17 에 들어간 template deduction guide
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
take_view(T&& t, std::size_t)->take_view< std::views::all_t<T> >;


template<typename T>
class reverse_view : public std::ranges::view_base
{
	T rng;
public:
	reverse_view(T& r) : rng(r) {};

	auto begin() { return std::make_reverse_iterator(rng.end()); }
	auto end() { return std::make_reverse_iterator(rng.begin()); }
};
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
reverse_view(T&& t, std::size_t)->reverse_view< std::views::all_t<T> >;

int main()
{
	std::vector v = { 1,2,3 };

	reverse_view rv(v);

	take_view tv1(v, 2);// 멤버로 ref_view<vector>
	take_view tv2(rv, 2);// 멤버로 rev_view<reverse_view>가 아니라 reverse_view 복사본 포함

	std::cout << typeid(tv1).name() << std::endl;
	std::cout << typeid(tv2).name() << std::endl;
}

 

  • view의 중첩을 실제로 사용해보자
#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view : public std::ranges::view_base
{
	T rng;
	std::size_t count;
public :
	take_view(T r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};
// C++17 에 들어간 template deduction guide
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
take_view(T&& t, std::size_t)->take_view< std::views::all_t<T> >;


template<typename T>
class reverse_view : public std::ranges::view_base
{
	T rng;
public:
	reverse_view(T& r) : rng(r) {};

	auto begin() { return std::make_reverse_iterator(rng.end()); }
	auto end() { return std::make_reverse_iterator(rng.begin()); }
};
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
reverse_view(T&& t, std::size_t)->reverse_view< std::views::all_t<T> >;

int main()
{
	std::vector v = { 1,2,3,4,5,6,7,8,9,10 };

	reverse_view rv(v);
	take_view tv(rv, 3);

	for(auto e : tv)
		std::cout << e << std::endl;
	for (auto e : take_view(reverse_view(v), 3) )
		std::cout << e << std::endl;
}

 

  • view 의 각종 함수들을 모아둔 view_interface를 구현
    • CRTP 테크닉을 사용
#include<vector>
#include<iostream>
#include<ranges>

// view interface : view 에 필요한 함수들을 모아둠
template<typename T>
class view_interface : public std::ranges::view_base
{
	T& Cast() { return static_cast<T&>(*this); }
public :
//	int size() { return this->end() - this->begin(); }
//	int size() { return static_cast<take_view*>(this)->end() - static_cast<take_view*>(this)->begin(); }
//	int size() { return static_cast<T*>(this)->end() - static_cast<T*>(this)->begin(); }
	size_t size() { return Cast().end() - Cast().begin(); }
};

template<typename T>
class take_view : public view_interface<take_view<T>>
{
	T rng;
	std::size_t count;
public :
	take_view(T r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};
// C++17 에 들어간 template deduction guide
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
take_view(T&& t, std::size_t)->take_view< std::views::all_t<T> >;


template<typename T>
class reverse_view : public view_interface<reverse_view<T>>
{
	T rng;
public:
	reverse_view(T& r) : rng(r) {};

	auto begin() { return std::make_reverse_iterator(rng.end()); }
	auto end() { return std::make_reverse_iterator(rng.begin()); }
};
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
reverse_view(T&& t, std::size_t)->reverse_view< std::views::all_t<T> >;

int main()
{
	std::vector v = { 1,2,3,4,5,6,7,8,9,10 };

	take_view tv(v, 3);

	std::cout << v.size() << std::endl;	// 10
	std::cout << tv.size() << std::endl;// 3

	reverse_view rv(v);
	std::cout << rv.size() << std::endl;// 10
}

 

  • C++20 의 표준에 제공되는 view_interface 를 사용해보자
#include<vector>
#include<iostream>
#include<ranges>

template<typename T>
class take_view : public std::ranges::view_interface<take_view<T>>
{
	T rng;
	std::size_t count;
public :
	take_view(T r, std::size_t c) : rng(r), count(c) {};

	auto begin() { return rng.begin(); }
	auto end() { return rng.begin() + count; }
};
// C++17 에 들어간 template deduction guide
template<typename T>
//take_view(T&& t, std::size_t)->take_view< std::remove_cvref_t<decltype(std::views::all(t))> >;
take_view(T&& t, std::size_t)->take_view< std::views::all_t<T> >;

int main()
{
	std::vector v = { 1,2,3,4,5,6,7,8,9,10 };

	take_view tv(v, 3);

	std::cout << v.size() << std::endl;
	std::cout << tv.empty() << std::endl;
	std::cout << tv.data() << std::endl;
}