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
    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. – WhozCraig Nov 18 '19 at 09:32
  • @WhozCraig I used code similar to this to look up Foo-like objects by their 'ID' (=index) in their vector. – nada Nov 18 '19 at 09: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 :) –  Nov 18 '19 at 09: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. – WhozCraig Nov 18 '19 at 09:55

4 Answers4

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());
}
nada
  • 2,109
  • 2
  • 16
  • 23
1

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

Arne J
  • 415
  • 2
  • 9
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.

mfnx
  • 2,894
  • 1
  • 12
  • 28
  • This wouldn't work, if there were more than just 1 array to put Foos in. – nada Nov 18 '19 at 09:51
  • 1
    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 Nov 18 '19 at 09:54
  • 1
    @nada I've seen plenty of `_someMember` in C++. It's certainly not "not very C-plus-plussy". – Max Langhof Nov 18 '19 at 10:03
  • @nada No, it wouldn't work. Obvious limitations... But, according to the question, foos.resize(2) should work. – mfnx Nov 18 '19 at 10:04
  • @MaxLanghof In that case I urge you to [abandon](https://stackoverflow.com/a/228848/4850111) [that](https://stackoverflow.com/a/20496955/4850111) [practise](https://softwareengineering.stackexchange.com/questions/191828/might-starting-variables-members-with-an-underscore-puzzle-the-compiler). – nada Nov 18 '19 at 10:07
  • @MFnx It's not always best to let askers continue on the path they choose. – nada Nov 18 '19 at 10:08
  • 1
    @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. – Max Langhof Nov 18 '19 at 10:10
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
Evg
  • 25,259
  • 5
  • 41
  • 83
  • 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 –  Nov 18 '19 at 12:04
  • @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**. – Evg Nov 18 '19 at 12:48