5

I'd like to write a template which get a container template as parameter (such as vector, set, unordered_set) and a type T and return a doubly linked container, that is each item of the container should contain a triple:

  • a T
  • a prev iterator pointing to some other triple of T
  • a next iterator pointing to some other triple of T

That is something like the following:

template <template <class Tr> class Container, class T>
struct Triple {
  T value;
  typename Container< Triple<Container, T> >::iterator prev, next;
};

template <template <class Tr> class Container, class T>
using DoublyLinkedContainer = Container< Triple< Container, T> >;

#include <vector>

// default partial specialisation of the Allocator parameter
template <class T> using SimpleVector = std::vector<T>;
DoublyLinkedContainer<SimpleVector, int> v;

It seems to be accepted by the compilers (gcc and clang), but I can't understand if I'm invoking undefined behavior as in Are C++ recursive type definitions possible, in particular can I put a vector<T> within the definition of T?

Edit: Here is some background as asked by @Richard Hodges:

I want to store a partition (in the mathematical sense) of a set of objects inside a container such that the equivalence classes associated to the partition are ordered. Therefore my idea is to make those equivalence classes as linked lists, since it suits my needs for fast removal and sequencial iteration. The set will be fixed when I will start to play with those equivalence classes, so that there is no problem with iterators being invalidated. Of course comparison, equality and hashes will only depend on the T attribute of the triple.

Right now I'm not sure which container will be better for my algorithm. Therefore, I'm trying to write such a template to postpone the choice. I'll be able to change the container at the end.

Note: I could as well use a map associating two iterators to a T and boost::flat_set if I want the equivalent of a vector but this is completely orthogonal to the template question raised here.

Community
  • 1
  • 1
hivert
  • 10,579
  • 3
  • 31
  • 56
  • 2
    ok... first question: what problem are you really trying to solve? – Richard Hodges May 01 '14 at 19:30
  • I think there are two questions here: Can the STL containers work with incomplete types? No, they're not required to; but yes in practice. The other question is whether accessing the `iterator` in `Triple` is well-defined, since it requires instantiating `Container` (which might require instantiating `Triple`). – dyp May 01 '14 at 19:30
  • You've answered your own question here: in general the standard does not define behavior for programs that instantiate standard library templates with incomplete types. Some popular implementaions do support it as a QoI issue, but it's not guaranteed. If your goal here is to combine an associative and a sequence container, you may be better served by creating a class that aggregates an associative and a sequence container. – Casey May 01 '14 at 19:31
  • Related: http://stackoverflow.com/q/17478621/420683 – dyp May 01 '14 at 19:32
  • @Casey: So you confirm that I'm indeed invoking UB ! – hivert May 01 '14 at 19:55
  • @hivert Yes, absolutely: `Triple` is incomplete at the point of instantiation of `Container` which [can lead to all sorts of shenanigans](http://coliru.stacked-crooked.com/a/8b1d0d17acffa5a6). – Casey May 01 '14 at 20:20
  • Not just that, but it looks like convoluted logic. storing a vectors iterators is an error since they are potentially invalidated on every modification of the vector. – Richard Hodges May 01 '14 at 20:21
  • @Richard Hodges: As I said, this is not a problem: The set will be fixed when I will start to play with the equivalence classes. – hivert May 01 '14 at 20:38
  • right, so why not just store a std::list, where the size_t is an index into the vector? Or std::list::const_iterator> ? The std::list is already a doubly-linked list. Why reinvent? – Richard Hodges May 01 '14 at 21:57
  • @Richard Hodges: Say I got a `T`. I want to know its next element from the list. How do I get that without going through the list ? The idea is to use the container to find it and then the list to find the next element. – hivert May 01 '14 at 22:07
  • posted an answer which solves the problem I think you have. – Richard Hodges May 01 '14 at 22:53

1 Answers1

1

Here is a solution to what I think is the problem you're trying to solve.

vec is the original immutable vector of Something objects (this is like your T, above).

weightedIndex is a vector of interators into vec which in this case has been ordered by ascending Something.weight() (but it could be any predicate)

#include <iostream>
#include <vector>
#include <algorithm>

struct Something
{
    Something(int weight)
    : _creationOrder { _createCount++ }
    , _weight { weight }
    {}

    int weight() const { return _weight; }

    std::ostream& write(std::ostream& os) const {
        os << "Something { createOrder=" 
        << _creationOrder 
        << ", weight=" << _weight << "}";
        return os;
    }
private:
    int _creationOrder;
    int _weight;
    static int _createCount;
};

std::ostream& operator<<(std::ostream& os, const Something& st) {
    return st.write(os);
}

int Something::_createCount { 0 };

using namespace std;

int main()
{
    vector<Something> vec { 10, 23, 76, 12, 98, 11, 34 };
    cout << "original list:";
    for(const auto& item : vec) {
        cout << "\n" << item;
    }

    using iter = decltype(vec)::const_iterator;
    vector<iter> weightIndex;
    weightIndex.reserve(vec.size());
    for(auto i = vec.begin() ; i != vec.end() ; ++i) {
        weightIndex.push_back(i);
    }
    sort(weightIndex.begin(), weightIndex.end() , [](const iter& i1, const iter& i2) {
       return i1->weight() < i2->weight(); 
    });

    // weightIndex is now a vector of pointers to the Something elements, but the pointers
    // are ordered by weight of each Something

    cout << "\nSorted index:";
    for(const auto p : weightIndex) {
        cout << "\n" << *p;
    }

    cout << endl;

    // find the mid-weight
    auto ii = next(weightIndex.begin(), 3);

    // next one in list is
    auto next_ii = next(ii, 1);

    // find previous in weighted order
    auto prev_ii = prev(ii, 1);
    cout << "Selection:\n";
    cout << "Current = " << **ii << endl;
    cout << "Next = " << **next_ii << endl;
    cout << "Previous = " << **prev_ii << endl;


   return 0;
}

Output:

original list:
Something { createOrder=0, weight=10}
Something { createOrder=1, weight=23}
Something { createOrder=2, weight=76}
Something { createOrder=3, weight=12}
Something { createOrder=4, weight=98}
Something { createOrder=5, weight=11}
Something { createOrder=6, weight=34}
Sorted index:
Something { createOrder=0, weight=10}
Something { createOrder=5, weight=11}
Something { createOrder=3, weight=12}
Something { createOrder=1, weight=23}
Something { createOrder=6, weight=34}
Something { createOrder=2, weight=76}
Something { createOrder=4, weight=98}
Selection:
Current = Something { createOrder=1, weight=23}
Next = Something { createOrder=6, weight=34}
Previous = Something { createOrder=3, weight=12}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • So what you are suggesting is to keep with the objects an integer (here `creationOrder`) registering their rank inside the original vector. And then using this index to search in a vector of iterator on the original vector. This basically solve my problem with one extra level of indirection... Good idea. – hivert May 01 '14 at 23:06
  • 1
    If you want to avoid indirection you could simply sort the original container, or copy the objects into a new ordered container. The integer creationOrder is just there for illustration, so you can see the effect of sorting the iterators. The actual sorting is done by weight() in this example. – Richard Hodges May 01 '14 at 23:12