9

I am trying to create a concept ElementIterable which can determine the type is nested ranges or not. For example, the elements in std::vector<int> is not iterable, but the elements (std::vector<int>) in std::vector<std::vector<int>> is iterable. The idea about using std::iterator_traits<T> comes up in my mind and the experimental code is as following. However, this ElementIterable concept doesn't work as the expected behavior. Is there any idea to fix this ElementIterable concept?

template<typename T>
concept ElementIterable = requires(typename std::iterator_traits<T>::value_type x)                        //  requires-expression
{
    x.begin();          // must have `x.begin()`
    x.end();            // and `x.end()`
};

The usage of this ElementIterable is here.

template<typename T> requires ElementIterable<T>
void Foo(T input);

template<typename T> requires ElementIterable<T>
void Foo(T input)
{
    std::cout << "Element iterable" << std::endl;
}

template<typename T>
void Foo(T input);

template<typename T>
void Foo(T input)
{
    std::cout << "Element not iterable" << std::endl;
}

The usage of the function Foo.

int number = 1;
    
std::vector<decltype(number)> vector1;
vector1.push_back(number);
Foo(vector1);           //  Element not iterable

std::vector<decltype(vector1)> vector2;
vector2.push_back(vector1);
Foo(vector2);           //  Element not iterable
                        //      but expected behaviour is: Element iterable

All suggestions are welcome.

YSC
  • 38,212
  • 9
  • 96
  • 149
JimmyHu
  • 403
  • 1
  • 8
  • 20
  • 1
    Why not simply check for `v.begin()->begin()` and `v.end()->end()` on the type itself, `T`? – Blindy Oct 21 '20 at 15:17
  • I think this question title should be renamed to be more verbose. Beginners are searching for a concept which simply checks if something is iterable, which is built in via std::ranges::range, but are instead finding this specialised concept instead. Searching "c++ iterable concept" puts this question at the top. – Ryan_DS Apr 17 '23 at 01:54

3 Answers3

8

If you want to ask if a type is a range which itself contains a range, that's simply applying the std::range type twice:

template<typename T>
concept nested_range = std::ranges::range<T> && std::ranges::range<std::ranges::range_value_t<T>>

range_value_t extracts the value_type from the iterator type of the range. Here's a live example.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
7

Well, the problem is std::iterator_traits<T>. The argument for iterator_traits is supposed to be an iterator type. Meanwhile, you want the concept to be applicable unto containers. Since std::iterator_traits is designed to be SFINAE friendly, and it's unlikely a container will satisfy enough of the legacy iterator concept, it's more than likely std::iterator_traits<T> has no members when you check your concept. That leads to the concept not being satisfied.

Why not rely on the concepts in the ranges header? It has a handy utility to obtain the value type of a type that satisfies the range concept

#include <ranges>

template<typename T>
concept ElementIterable = requires(std::ranges::range_value_t<T> x)
{
    x.begin();          // must have `x.begin()`
    x.end();            // and `x.end()`
};

Or, slightly more robust and without reinventing standard traits

template<typename T>
concept ElementIterable = std::ranges::range<std::ranges::range_value_t<T>>;
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
5

C++20 concepts can be as dumb (as in, text replacing) as you need them to be. In your case, simple template duck typing should do the job by checking for what you need to exist, ie iterator functions in the results of your type's iterator functions.

With that in mind, you can try something like this:

template<typename T>
concept ElementIterable = requires(T x)
{
    x.begin()->begin();        
    x.end()->end();            
};
Blindy
  • 65,249
  • 10
  • 91
  • 131
  • 1
    Yeah fixed it, was a left-over from copying the provided code – Blindy Oct 21 '20 at 15:33
  • 3
    This is a pretty poor concept. It doesn't use any existing standard library concepts, and thus can't subsume them. All it checks for is the presence of *memberwise* `begin/end`, not `ranges::begin/end` which allows for other things. Overall, this just isn't a good way to resolve the issue. – Nicol Bolas Oct 21 '20 at 15:39