18

Is there a way to check if a sequence container is contiguous in memory? Something like:

#include <iostream>
#include <vector>
#include <deque>
#include <array>

int main()
{
    std::cout << std::boolalpha;
    std::cout << is_contiguous<std::vector<int>>::value   << '\n'  // true
    std::cout << is_contiguous<std::deque<int>>::value    << '\n'; // false
    std::cout << is_contiguous<std::array<int, 3>>::value << '\n'; // true
}

Clarification

This question is referring to type traits, rather than the properties of a specific instance of a type.

Daniel
  • 8,179
  • 6
  • 31
  • 56
  • 1
    Not at this point. The term "contiguous iterator" is only defined as an editorial tool to make the specification more compact, but it's not a queriable trait. – Kerrek SB Jan 25 '16 at 23:48
  • 3
    You may still create a custom traits with all the required specializations... – Jarod42 Jan 25 '16 at 23:50
  • 1
    @BaummitAugen Possible use case could be for extracting stream input into templated sequence container. With a contiguous container, `std::basic_istream::getline` can be used directly (e.g. `in.getline(&container[0], 10)`). – Daniel Jan 25 '16 at 23:56
  • @Daniel: That's what the fake iterators like `insert_iterator` are for – Lightness Races in Orbit Jan 25 '16 at 23:59
  • 8
    The existence of `.data()` returning a pointer might be a reasonable proxy. – T.C. Jan 26 '16 at 01:36
  • @T.C. That seems to be the best solution without better language support. – Daniel Jan 26 '16 at 10:50
  • @Daniel I think you have an answer: a resounding 'no'. Regardless of whether or not you _like_ the answer, you should still accept one. It seems based on comments that TemplateRex's answer was most helpful. – erip Feb 01 '16 at 13:02
  • @T.C. , I agree that yours is the best solution. Do you know if a similar trick can be applied to checking if an iterator is contiguous? (I personally think that for consistency, iterators with contiguous access should themselves have a `.data()` member or a `data(ContIt)` function). – alfC Mar 23 '17 at 03:13

3 Answers3

16

No, there is not compiletime trait for this.

The draft C++1z Standard defines contiguity as a runtime property of an iterator range. Note there is no compiletime std::contiguous_iterator_tag corresponding to this iterator category.

24.2 Iterator requirements [iterator.requirements]

24.2.1 In general [iterator.requirements.general]

5 Iterators that further satisfy the requirement that, for integral values n and dereferenceable iterator values a and (a + n), *(a + n) is equivalent to *(addressof(*a) + n), are called contiguous iterators. [ Note: For example, the type “pointer to int” is a contiguous iterator, but reverse_iterator<int *> is not. For a valid iterator range [a,b) with dereferenceable a, the corresponding range denoted by pointers is [addressof(*a),addressof(*a) + (b - a)); b might not be dereferenceable. — end note ]

One way to test for this at runtime would be

#include <array>
#include <deque>
#include <list>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>

template<class I>
auto is_contiguous(I first, I last)
{ 
    auto test = true;
    auto const n = std::distance(first, last);
    for (auto i = 0; i < n && test; ++i) {
        test &= *(std::next(first, i)) == *(std::next(std::addressof(*first), i));
    }        
    return test;        
}

int main()
{
    auto l = std::list<int> { 1, 2, 3 };
    auto m = std::map<int, int>  { {1, 1}, {2,2}, {3,3} };
    auto u = std::unordered_multiset<int> { 1, 1, 1 };
    auto d = std::deque<int>(4000);
    int c[] = { 1, 2, 3 };
    auto a = std::array<int, 3> {{ 1, 2, 3 }};
    auto s = std::string {"Hello world!"};
    auto v = std::vector<int> { 1, 2, 3, };

    std::cout << std::boolalpha << is_contiguous(l.begin(), l.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(m.begin(), m.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(u.begin(), u.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(d.begin(), d.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(d.begin(), d.begin() + 1000) << "\n";
    std::cout << std::boolalpha << is_contiguous(std::begin(c), std::end(c)) << "\n";
    std::cout << std::boolalpha << is_contiguous(a.begin(), a.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(s.begin(), s.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(v.begin(), v.end()) << "\n";
    std::cout << std::boolalpha << is_contiguous(v.rbegin(), v.rend()) << "\n";
}

Live Example. This prints false for the list, map and unordered_multimap, and true for the C-array, and the std::array, string and vector. It prints true for small subranges within a deque and false for large subranges. It also prints false for an iterator range consisting of reverse iterators.

UPDATE: as commented by @T.C. the original N3884 proposal did have a

struct contiguous_iterator_tag : random_access_iterator_tag {};

so that tag-dispatching on iterator categories would not break. However, this would have broken non-idiomatic code with class template specializations on random_access_iterator_tag. The current draft hence does not contain a new iterator category tag.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • You forgot to mention that contiguous iterators as an iterator category are not part of any released standard yet. So for C++11 and C++14, this concept is not queryable by traits or something else. – Jens Jan 26 '16 at 07:57
  • 4
    Also noteworthy is that `std::deque` will return true until reaching a certain size, then will subsequently return false. – ildjarn Jan 26 '16 at 08:23
  • @ildjarn updated with tests for `deque` (small and big subrange) and also for reverse iterators. – TemplateRex Jan 26 '16 at 10:22
  • @Jens updated to reflect that this is a C++1z feature. Note that it is not a compiletime but a runtime property of iterator ranges. – TemplateRex Jan 26 '16 at 10:23
  • @TemplateRex It seems a shame to me that it's defined that way; I can't think of a good reason why this can't - and shouldn't - be compile time decidable. – Daniel Jan 26 '16 at 10:36
  • @Daniel it would require quite a lot of infrastructure, and would also depend on the allocator. E.g one can imagine an allocator that puts list nodes in contiguous memory, but only if the amount of nodes is smaller than a certain number, and fall back to the heap otherwise (Howard Hinnant's [stack allocator e.g.](https://howardhinnant.github.io/stack_alloc.html)). So this seems to be a quintessential runtime property. Also the example for `deque` shows that it depends on the iterators whether a subrange within a container is contiguous. – TemplateRex Jan 26 '16 at 10:39
  • 1
    The original proposal would have added a `contiguous_iterator_tag`, but ended up not doing it because it would break existing code. – T.C. Jan 26 '16 at 12:06
  • 1
    @T.C. true, but that still would have left `deque` a bit in the lurch. – TemplateRex Jan 26 '16 at 12:12
7

No. The C++ Standard guarantees there are no false negatives. (i.e., std::vector, std::string, std::array, and basic arrays are promised to be stored contiguously).

However, the C++ Standard doesn't guarantee there are no false positives.

int main() {
   std::unique_ptr<Node> n1(new Node);
   std::unique_ptr<Node> n2(new Node);
   n1->next = n2; // n1 and n2 might be contiguous, but might not be
}

Thus, your type trait could be wrong some of the time. If it's wrong some of the time, it's not a type trait; rather, it's an instance trait.

erip
  • 16,374
  • 11
  • 66
  • 121
  • 2
    A trait implemented like this would be unusable anyway for any container with zero or one elements in it. Probably why nobody's suggested it. So we're back to just "no": the only way such a trait could exist would be built in to the standard library, and one is not. – Lightness Races in Orbit Jan 26 '16 at 00:24
  • @LightnessRacesinOrbit Right. The only naïve implementation would be to check if it's a `std::vector`, `std::array`, `std::string` or basic array; but that is so flimsy that I didn't even want to add it to my answer. – erip Jan 26 '16 at 00:26
  • 1
    But is that really false? If I verify that every element other than the first in a container is adjacent to the previous element, then is not *that particular container at that moment* contiguous? (And I know it won't change if I make no changes to the container.) – rici Jan 26 '16 at 00:27
  • What if I "prove" that it is all contiguous, then add an element that isn't, then use a method that relies on all contiguous elements? – erip Jan 26 '16 at 00:29
  • 2
    @rici: Yes but that would be a property of your container instance, not a trait of its type. And, therefore, not what was asked for. – Lightness Races in Orbit Jan 26 '16 at 00:29
  • 1
    @lightness: the op asks about a container, which is at best ambiguous. I think the more natural interpretation is that it asks about a container, rather than a class which lets you create containers. But your interpretation is also valid. – rici Jan 26 '16 at 00:33
  • 1
    @erip: that would be just as undefined as modifying a container in the middle of a range for over the container. – rici Jan 26 '16 at 00:34
  • I'm not sure I agree, but I'm not willing to argue. :) – erip Jan 26 '16 at 00:36
  • 2
    @lightness: actually, looking at the examples in the OP, I can see the strength of your interpretation. – rici Jan 26 '16 at 00:37
  • 4
    I disagree. While an "is this container's data stored contiguously?" type trait is impossible, an "is this container's data **guaranteed to be** stored contiguously?" is quite reasonable and could be useful. – user253751 Jan 26 '16 at 00:38
  • 1
    @erip: it's the kind of question which invites devil's advocacy :-) – rici Jan 26 '16 at 00:39
  • @immibis That's not what OP asked. – erip Jan 26 '16 at 00:40
  • @immibis In any case, I've already addressed how one might implement a "guaranteed-to-be-contiguous" trait in my comments. – erip Jan 26 '16 at 00:42
  • 1
    @LightnessRacesinOrbit Oh no, it seems some of your downvotes have leaked over to me. ;) – erip Jan 26 '16 at 00:51
  • 1
    @erip: Haha there's just no justice any more – Lightness Races in Orbit Jan 26 '16 at 00:51
  • 1
    @erip I don't how immibis's interpretation is different to what I've asked; data contiguity is a trait of the type if and only if the data of any instance of that type is (guaranteed to be) contiguous. – Daniel Jan 26 '16 at 10:22
  • 1
    @Daniel Indeed, and that's what I've addressed. The only types that are ever guaranteed to be contiguous are `std::vector`, `srd::array`, `std::string`, and basic array. There's a huge difference between an instance being contiguous and a type being contiguous. – erip Jan 26 '16 at 12:24
-4

No.​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055