2

Is there any way I can pass vector's index to an constructor of it's element? for example:

#include <iostream>
#include <vector>

class Foo {
 public:
  Foo(unsigned long index) {
    std::cout << index << std::endl;
  }
};

int main() {
  std::vector<Foo> foo;
  foo.resize(2); // any way to make this work?
}

this code does of cause not work because the compiler don't know how to construct a Foo(unsigned long index), but any way I can do some tricks(for example to custom an allocator?) to make this code actually work?

4
  • 4
    Without a compliant default-ctor, no, there is no way to make that work. Foo is only constructible with a provided ctor-argument. I'm curious, however, what is the real problem this solution is attempting to solve, because chances are there lays a more proper solution to that, than trying to make this work. Commented Nov 18, 2019 at 9:32
  • @WhozCraig I used code similar to this to look up Foo-like objects by their 'ID' (=index) in their vector. Commented Nov 18, 2019 at 9:40
  • @WhozCraig my element is a special data structure which happens have relation ship with the index, I just curious if I can do in this way, so I asked :) Commented Nov 18, 2019 at 9:51
  • 1
    @reavenisadesk I understand. Depending on the nature of where Foo's come from, and how they are managed (are there more than one foo floating around in various places? can Foo's be moved from one foo to another? what are the expectations when a Foo is removed from a foo and thereafter all subsequent indices no longer align?), an alternative identity-mapping may be worth considering. Anyway, thanks for clarifying. Commented Nov 18, 2019 at 9:55

4 Answers 4

2

You can add elements in a for loop and pass the index as an argument to their ctors like this:

// Init your vector + optionally reserve space
std::vector<Foo> foo;
const unsigned elements_to_add = 5; // or whatever number
foo.reserve(foo.size() + elements_to_add);

// foo.size() will be passed as parameter to the ctor you defined
for (std::size_t i = 0; i < elements_to_add; i++) {
    foo.emplace_back(foo.size());
}
Sign up to request clarification or add additional context in comments.

2 Comments

But foo.resize(2); does not give the desired result.
@MFnx Well one does not have to use resize to fill an array.
1

No, you will want to use std::generate() , or std::generate_n() in combination with std::back_inserter().

1 Comment

Probably more std::generate_n, with std::back_inserter.
0

You can write a custom statefull allocator that will pass an index when an object is constructed.

Minimal example:

template<class T>
class Allocator {
public:
    using value_type = T;

    T* allocate(std::size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t) noexcept {
        ::operator delete(p);
    }

    template<class... Args>
    void construct(T* p, Args&&... args) {
        ::new(static_cast<void*>(p)) T(counter_++, std::forward<Args>(args)...);
    }

    void destroy(T* p) noexcept {
        p->~U();
        --counter_;
    }

private:
    std::size_t counter_ = 0;
};

Usage example:

struct Foo {
    Foo(std::size_t index) {
        std::cout << index << ' ';
    }

    Foo(std::size_t index, const Foo& other) : Foo(other) {
        std::cout << index << ' ';
    }
};

std::vector<Foo, Allocator<Foo>> foos1;
foos1.resize(3);
std::cout << std::endl;

std::vector<Foo, Allocator<Foo>> foos2;
foos2.resize(4);

// Output: 
// 0 1 2 
// 0 1 2 3

2 Comments

But it seems that we can't guarantee that counter_ is the index, though I cannot come up with an example yet. But you gave me an idea, thanks
@reavenisadesk, this solution relies on the particular order of object construction when resize() is called. The standard just says that resize() "appends" (n - size()) elements to the sequence. libstdc++ does something like this: for (; n > 0; --n, ++cur) traits::construct(alloc, addressof(*cur));. But AFAIK, this order is not guaranteed by the standard. So, this answer is more of a trick than a solution that can be used in real code. A reliable solution is given by nada.
0

I guess there are many ways to get more or less what you want. But sooner or later, you'll probably find out that you won't need this.

Here is a possible solution:

#include <iostream>
#include <vector>

class Foo
{
    inline static unsigned long _static_index = 0;
    unsigned long _index;

public:
    Foo() : _index(_static_index) { ++_static_index; }

    auto index() const { return _index; }
    static void resetIndex() { _static_index = 0; }
};

int main()
{
    std::vector<Foo> foos;
    Foo::resetIndex();
    foos.resize(2);

    for (const auto& f : foos)
        std::cout << f.index() << std::endl;

    return 0;
}

So, you just increment a static counter and assign it to the private member _index. This obviously has its limitations. For instance, say you create 3 instances of Foo before filling your vector of Foos, then foos[0].index() would return 3 instead of 0. So, before filling foos, you would need to reset the _static_index.

7 Comments

This wouldn't work, if there were more than just 1 array to put Foos in.
Also don't forget to inline _static_index, else you have to initialize it somewhere else. And starting variables' names with _ isn't very C-plus-plussy.
@nada I've seen plenty of _someMember in C++. It's certainly not "not very C-plus-plussy".
@nada No, it wouldn't work. Obvious limitations... But, according to the question, foos.resize(2) should work.
@nada 1. Underscore + lower case letter is ok for identifiers outside the global namespace (i.e. for class members) so this is 100% legal, 2. this isn't my choice for any of the places I've seen it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.