다음을 통해 공유


<ranges> 개념

개념은 컴파일 시간에 템플릿 매개 변수를 제한하는 C++20 언어 기능입니다. 잘못된 템플릿 인스턴스화를 방지하고, 읽을 수 있는 형식으로 템플릿 인수 요구 사항을 지정하고, 더 간결한 템플릿 관련 컴파일러 오류를 제공하는 데 도움이 됩니다.

다음 예제에서는 나누기를 지원하지 않는 형식으로 템플릿을 인스턴스화하지 않도록 하는 개념을 정의합니다.

// requires /std:c++20 or later
#include <iostream>

// Definition of dividable concept which requires 
// that arguments a & b of type T support division
template <typename T>
concept dividable = requires (T a, T b)
{
    a / b;
};

// Apply the concept to a template.
// The template will only be instantiated if argument T supports division.
// This prevents the template from being instantiated with types that don't support division.
// This could have been applied to the parameter of a template function, but because
// most of the concepts in the <ranges> library are applied to classes, this form is demonstrated.
template <class T> requires dividable<T>
class DivideEmUp
{
public:
    T Divide(T x, T y)
    {
        return x / y;
    }
};

int main()
{
    DivideEmUp<int> dividerOfInts;
    std::cout << dividerOfInts.Divide(6, 3); // outputs 2
    // The following line will not compile because the template can't be instantiated 
    // with char* because char* can be divided
    DivideEmUp<char*> dividerOfCharPtrs; // compiler error: cannot deduce template arguments 
}

컴파일러 스위치 /diagnostics:caret 를 Visual Studio 2022 버전 17.4 미리 보기 4 이상으로 전달하면 개념 dividable<char*> 이 false로 평가되는 오류는 실패한 식 요구 사항을 (a / b) 직접 가리킵니다.

범위 개념은 네임스페이 std::ranges 스에 정의되고 헤더 파일에 선언됩니다 <ranges> . 범위 어댑터, 등의 선언에 사용됩니다.

범위는 6가지 범주입니다. 개념에 나열된 <iterator> 반복기의 범주와 관련이 있습니다. 기능 증가 순서에 따라 범주는 다음과 같습니다.

범위 개념 설명
output_range
input_range
쓸 수 있는 범위를 지정합니다.
한 번 읽을 수 있는 범위를 지정합니다.
forward_range 여러 번 읽고 쓸 수 있는 범위를 지정합니다.
bidirectional_range 앞뒤로 읽고 쓸 수 있는 범위를 지정합니다.
random_access_range 인덱스로 읽고 쓸 수 있는 범위를 지정합니다.
contiguous_range 요소가 메모리에서 순차적이고 크기가 같으며 포인터 산술 연산을 사용하여 액세스할 수 있는 범위를 지정합니다.

앞의 표에서는 기능 향상 순서대로 개념이 나열됩니다. 개념의 요구 사항을 충족하는 범위는 일반적으로 앞의 행에 있는 개념의 요구 사항을 충족합니다. 예를 들어, a random_access_range 는 , forward_range, input_rangeoutput_range.bidirectional_range 예외는 input_range쓸 수 없으므로 output_range.

Diagram of the ranges iterator hierarchy. input_range and output_range are the most basic iterators. forward_range is next and refines both input_range and output_range. bidirectional_range refines forward_range. random_access_range refines bidirectional_range. Finally, contiguous_range refines random_access_range

기타 범위 개념은 다음과 같습니다.

범위 개념 설명
rangeC++20 반복기와 sentinel을 제공하는 형식을 지정합니다.
borrowed_rangeC++20 범위 반복기의 수명이 범위의 수명에 연결되지 않도록 지정합니다.
common_rangeC++20 범위 반복기의 형식과 범위의 sentinel 형식이 같게 지정합니다.
Simple_ViewC++20 표준 라이브러리의 일부로 정의된 공식 개념이 아니라 일부 인터페이스에서 도우미 개념으로 사용됩니다.
sized_rangeC++20 요소 수를 효율적으로 제공할 수 있는 범위를 지정합니다.
viewC++20 효율적인(일정한 시간) 이동 생성, 할당 및 소멸이 있는 형식을 지정합니다.
viewable_rangeC++20 뷰이거나 뷰로 변환할 수 있는 형식을 지정합니다.

bidirectional_range

A bidirectional_range 는 범위를 앞뒤로 읽고 쓸 수 있습니다.

template<class T>
concept bidirectional_range =
    forward_range<T> && bidirectional_iterator<iterator_t<T>>;

매개 변수

T
테스트할 형식이 .인지 확인합니다 bidirectional_range.

설명

이러한 종류의 범위가 지원되거나 그 이상입니다 bidirectional_iterator . A bidirectional_iterator 에는 A의 forward_iterator기능이 있지만 뒤로 반복할 수도 있습니다.

의 몇 가지 예는 bidirectional_rangestd::set, std::vectorstd::list.

borrowed_range

형식은 개체에서 가져온 반복기의 유효성이 개체의 수명보다 오래 지속될 수 있는지를 모델 borrowed_range 화합니다. 즉, 범위가 더 이상 존재하지 않는 경우에도 범위에 대한 반복기를 사용할 수 있습니다.

template<class T>
concept borrowed_range =
    range<T> &&
    (is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);

매개 변수

T
테스트할 형식이 .인지 확인합니다 borrowed_range.

설명

rvalue 범위의 수명은 범위 모델 borrowed_range 여부에 관계없이 함수 호출 후에 끝날 수 있습니다. 이 경우 범위의 borrowed_range수명이 종료되는 시기에 관계없이 잘 정의된 동작으로 반복기를 계속 사용할 수 있습니다.

예를 들어 컨테이너와 같은 vector 경우 또는 list 컨테이너의 수명이 종료될 때 반복기가 제거된 요소를 참조하기 때문에 이러한 경우가 사실이 아닙니다.

예를 들어 viewiota_view<int>{0, 42} 반복기가 borrowed_range요청 시 생성되므로 삭제되지 않는 값 집합을 초과하여 반복기를 계속 사용할 수 있습니다.

common_range

반복기의 common_range 형식은 sentinel의 형식과 동일합니다. 즉, begin()end() 동일한 형식을 반환합니다.

template<class T>
concept common_range =
   ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;

매개 변수

T
테스트할 형식이 .인지 확인합니다 common_range.

설명

형식 std::ranges::begin()std::ranges::end() 을 가져오는 것은 두 반복기 사이의 거리를 계산하는 알고리즘과 반복기 쌍으로 표시된 범위를 허용하는 알고리즘에 중요합니다.

표준 컨테이너(예: vector)는 의 요구 사항을 충족합니다 common_range.

contiguous_range

요소의 contiguous_range 메모리에 순차적으로 저장 되 고 포인터 산술을 사용 하 여 액세스할 수 있습니다. 예를 들어 배열은 .입니다 contiguous_range.

template<class T>
concept contiguous_range =
    random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
    requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};

매개 변수

T
테스트할 형식이 .인지 확인합니다 contiguous_range.

설명

요소가 메모리에 순차적으로 배치되고 크기가 같기 때문에 포인터 산술 연산을 통해 A contiguous_range 에 액세스할 수 있습니다. 이러한 종류의 범위는 continguous_iterator모든 반복기 중에서 가장 유연한 범위를 지원합니다.

의 몇 가지 예는 contiguous_rangestd::array, std::vectorstd::string.

예: contiguous_range

다음 예제에서는 포인터 산술 연산을 사용하여 다음 항목 contiguous_range에 액세스하는 방법을 보여줍니다.

// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>

int main()
{
    // Show that vector is a contiguous_range
    std::vector<int> v = {0,1,2,3,4,5};
    std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true

    // Show that pointer arithmetic can be used to access the elements of a contiguous_range
    auto ptr = v.data();
    ptr += 2;
    std::cout << *ptr << '\n'; // outputs 2
}
true
2

forward_range

A는 forward_range 범위를 여러 번 읽고 쓸 수 있습니다.

template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;

매개 변수

T
테스트할 형식이 .인지 확인합니다 forward_range.

설명

이러한 종류의 범위가 지원되거나 그 이상입니다 forward_iterator . A는 forward_iterator 범위를 여러 번 반복할 수 있습니다.

input_range

input_range 번 읽을 수 있는 범위입니다.

template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;

매개 변수

T
테스트 input_range할 형식입니다.

설명

형식이 다음 요구 사항을 충족하는 경우 input_range:

  • 함수는 ranges::begin() .를 반환합니다 input_iterator. 정의되지 않은 동작의 input_range 결과로 두 번 이상 호출 begin() 합니다.
  • 반복을 역참조할 input_iterator 수 있으며 매번 동일한 값을 생성합니다. 다중 input_range 패스가 아닙니다. 반복기를 증분하면 복사본이 무효화됩니다.
  • 와 함께 ranges::for_each사용할 수 있습니다.
  • 더 이상 지원합니다 input_iterator .

output_range

output_range 은 작성할 수 있는 범위입니다.

template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;

매개 변수

R
범위의 형식입니다.

T
범위에 쓸 데이터의 형식입니다.

설명

의미 output_iterator<iterator_t<R>, T> 는 형식이 형식의 범위에 R형식 T 값을 쓸 수 있는 반복기를 제공한다는 것입니다. 즉, 지원하거나 그 이상입니다 output_iterator .

random_access_range

A는 random_access_range 인덱스별로 범위를 읽거나 쓸 수 있습니다.

template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;

매개 변수

T
테스트할 형식이 .인지 확인합니다 sized_range.

설명

이러한 종류의 범위가 지원되거나 그 이상입니다 random_access_iterator . A random_access_range 에는 , output_range, forward_rangebidirectional_range.input_range A random_access_range 는 정렬할 수 있습니다.

의 몇 가지 예는 random_access_rangestd::vector, std::arraystd::deque.

range

형식이 충족되어야 하는 요구 사항을 정의합니다 range. A range 는 해당 요소를 반복할 수 있도록 반복기와 센티넬을 제공합니다.

template<class T>
concept range = requires(T& rg)
{
  ranges::begin(rg);
  ranges::end(rg);
};

매개 변수

T
테스트할 형식이 .인지 확인합니다 range.

설명

요구 사항은 다음과 같습니다.range

  • 다음을 사용하여 std::ranges::begin() 반복할 수 있습니다. std::ranges::end()
  • ranges::begin() 분할 ranges::end() 상환 상수 시간에 실행하고 수정하지 range않습니다. 분할 상환 상수 시간은 O(1)를 의미하지는 않지만, 최악의 경우에도 일련의 호출에 대한 평균 비용은 O(n^2) 또는 더 나쁜 것이 아니라 O(n)입니다.
  • [ranges::begin(), ranges::end()) 는 유효한 범위를 표시합니다.

Simple_View

A Simple_View 는 일부 ranges 인터페이스에서 사용되는 박람회 전용 개념입니다. 라이브러리에 정의되어 있지 않습니다. 일부 범위 어댑터의 동작을 설명하는 데 도움이 되도록 사양에만 사용됩니다.

template<class V>
  concept Simple_View = // exposition only
    ranges::view<V> && ranges::range<const V> &&
    std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
    std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;

매개 변수

V
테스트할 형식이 .인지 확인합니다 Simple_View.

설명

보기 VSimple_View 다음이 모두 true인 경우입니다.

  • V 은 뷰입니다.
  • const V 은 범위입니다.
  • const V 둘 다 v 동일한 반복기 및 sentinel 형식을 가집니다.

sized_range

A sized_range 는 분할 상환 상수 시간에 범위의 요소 수를 제공합니다.

template<class T>
  concept sized_range = range<T> &&
    requires(T& t) { ranges::size(t); };

매개 변수

T
테스트할 형식이 .인지 확인합니다 sized_range.

설명

요구 사항은 sized_range 호출하는 ranges::size 것입니다.

  • 범위를 수정하지 않습니다.
  • 분할 상환 상수 시간의 요소 수를 반환합니다. 분할 상환 상수 시간은 O(1)를 의미하지는 않지만, 최악의 경우에도 일련의 호출에 대한 평균 비용은 O(n^2) 또는 더 나쁜 것이 아니라 O(n)입니다.

의 몇 가지 예 sized_range 는 다음과 같습니다std::vectorstd::list.

예: sized_range

다음 예제에서는 다음 중 int 하나는 vector 다음과 같습니다.sized_range

// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>

int main()
{
    std::cout << std::boolalpha << std::ranges::sized_range<std::vector<int>> << '\n'; // outputs "true"
}    

view

A view 에는 요소 수에 관계없이 일정한 시간 이동 생성, 할당 및 소멸 작업이 있습니다. 뷰를 복사 생성 가능하거나 할당 가능한 복사할 필요는 없지만, 뷰가 있는 경우 해당 작업도 일정한 시간에 실행해야 합니다.

일정한 시간 요구 사항 때문에 보기를 효율적으로 작성할 수 있습니다. 예를 들어 호출inputint 벡터, 숫자를 3으로 나눌 수 있는지 여부를 결정하는 함수, 숫자를 제곱하는 함수를 지정하면 문 auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square); 은 입력에 있는 숫자의 제곱을 3으로 나눌 수 있는 뷰를 효율적으로 생성합니다. 뷰를 함께 | 커넥트 보기를 작성이라고 합니다. 형식이 개념을 충족하는 view 경우 효율적으로 작성할 수 있습니다.

template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;

매개 변수

T
뷰인지 확인하기 위해 테스트할 형식입니다.

설명

보기를 구성 가능하게 만드는 필수 요구 사항은 이동/복사가 저렴하다는 것입니다. 이는 뷰가 다른 보기로 구성될 때 이동/복사되기 때문입니다. 이동 가능한 범위여야 합니다.

ranges::enable_view<T> 는 개념의 의미 체계 요구 사항을 view 준수하는 데 사용되는 특성입니다. 형식은 다음을 통해 옵트인할 수 있습니다.

  • 의 특수화에서 공개적으로 그리고 명확하게 파생 ranges::view_interface
  • 빈 클래스 ranges::view_base에서 공개적으로 및 명확하게 파생되거나
  • ranges::enable_view<T>true

또한 작성해야 하는 상용구 코드를 저장하는 기본 구현을 제공하기 때문에 view_interface 옵션 1이 선호됩니다.

실패하면 옵션 2가 옵션 3보다 약간 간단합니다.

옵션 3의 장점은 형식의 정의를 변경하지 않고도 가능하다는 것입니다.

viewable_range

A viewable_range 는 뷰이거나 뷰로 변환할 수 있는 형식입니다.

template<class T>
  concept viewable_range =
    range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);

매개 변수

T
테스트할 형식이 뷰인지 아니면 뷰로 변환할 수 있는지 확인합니다.

설명

범위를 보기로 변환하는 데 사용합니다 std::ranges::views::all() .

참고 항목

<ranges>
범위 어댑터
클래스 보기