Reputation: 39
It is easy to fill the elements of a vector by index after it has been initialized:
std::vector<int> myVector {0, 0, 0};
int indexOfInterest = 1;
myVector[indexOfInterest] = 999;
// myVector now contains {0, 999, 0}
However, is there a way to initialize a vector directly with the index? indexOfInterest may change in my code in the future, and I would like to avoid hard-coding the vector with
std::vector<int> myVector {0, 999, 0};
Is there some syntax like
int indexOfInterest = 1;
std::vector<int> myVector[3] {indexOfInterest : 999}; // this is made-up syntax!
// desired: myVector contains {0, 999, 0} and is 3 elements large
that can be used in C++11 to achieve this effect?
Upvotes: 0
Views: 4429
Reputation: 52489
Is there a way to initialize a vector by index...
In C there is: see What do square brackets mean in array initialization in C? , with something called "Designated Initializers".
In C++ there is not, but I have presented a work-around I like, below. See cppreference.com here, under the "Designated initializers" section near the bottom: https://en.cppreference.com/w/cpp/language/aggregate_initialization
Note: out-of-order designated initialization, nested designated initialization, mixing of designated initializers and regular initializers, and designated initialization of arrays are all supported in the C programming language, but are not allowed in C++.
struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order) int arr[3] = {[1] = 5}; // valid C, invalid C++ (array) struct B b = {.a.x = 0}; // valid C, invalid C++ (nested) struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed)
See also the GCC documentation here (emphasis added):
In ISO C99 you can give the elements in any order, specifying the array indices or structure field names they apply to, and GNU C allows this as an extension in C90 mode as well. This extension is not implemented in GNU C++.
And see also my question here: Initializing std::vector with square brackets [] inside; what is happening?
So, you can use std::map
or std::unordered_map
to do this, but I want really fast access, and accessing a contiguous piece of memory such as a C-style array, C++ std::array
, or C++ std::vector
via a simple index will be much faster. So, what I have settled on as a work-around solution is this:
In header.h:
WARNING: be sure to set the size! operator[]
for std::vector
does NOT automatically add members if you index past the end of the vector. See: https://en.cppreference.com/w/cpp/container/vector/operator_at:
(Regarding std::vector<T,Allocator>::operator[]
; emphasis added):
Unlike
std::map::operator[]
, this operator never inserts a new element into the container. Accessing a nonexistent element through this operator is undefined behavior.
#include <vector>
std::vector<int> v(5); // WARNING: set the size you need!
In source.cpp:
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
v[4] = 5;
This is not ideal because it doesn't allow you to make your vector const
or constexpr
, but that's okay. It's a worthwhile tradeoff I think in the case of trying to make fast maps with crystal clear mapping. Ex:
(Use a C style enum
here, NOT a C++ style enum class
--more on that in my answer here: How can I iterate over an enum?)
enum fruits
{
FRUITS_APPLE = 0,
FRUITS_BANANA,
FRUITS_PEAR,
FRUITS_LEMON,
FRUITS_KIWI,
/// Not a valid value; this is the number of members in this enum
FRUITS_count,
};
// Now you can initialize your std::vector<> in your .cpp file clearly
// (with crystal-clear enum-based indexing, or mapping) like this.
// Think of this as a key:value pair, with the enum being the key and
// the value of course being the value.
v[FRUITS_APPLE] = 1;
v[FRUITS_BANANA] = 2;
v[FRUITS_PEAR] = 3;
v[FRUITS_LEMON] = 4;
v[FRUITS_KIWI] = 5;
Note that if the object is a fixed size, you are better off using std::array
instead:
Declaration of the array, to map from enum fruits
to integers, for example:
#include <array>
std::array<int, FRUITS_count> fruitsArray;
Load it with values:
fruitsArray[FRUITS_APPLE] = 1;
fruitsArray[FRUITS_BANANA] = 2;
fruitsArray[FRUITS_PEAR] = 3;
fruitsArray[FRUITS_LEMON] = 4;
fruitsArray[FRUITS_KIWI] = 5;
Side note: if you ever need to make the data type constexpr
, std::array
and C-style arrays can both be made constexpr
, but std::vector
canNOT--it can only be const
at best.
Upvotes: 0
Reputation: 22152
In the comments you mention, that you need this in order to initialize the vector in a member initializer list.
You can delegate the construction to a function or lambda in the member initializer list:
some_constructor(...) : myVector([]{
std::vector<int> vec(3);
myVector[indexOfInterest] = 999;
return vec;
}()) {
}
You can also write yourself a generic function for the task, e.g.:
template<typename T, std::size_t N>
std::vector<T> make_vector(typename std::vector<T>::size_type size, const T& value, std::pair<typename std::vector<T>::size_type, T>(&&entries)[N]) {
std::vector<T> vec(size, value);
for(auto& entry : entries)
vec[entry.first] = std::move(entry.second);
return vec;
}
template<typename T, std::size_t N>
std::vector<T> make_vector(typename std::vector<T>::size_type size, std::pair<typename std::vector<T>::size_type, T>(&&entries)[N]) {
return make_vector<T>(size, {}, entries);
}
This can be used in the member initializer for example like this:
some_constructor(...) : myVector(make_vector(3, 0, {{indexOfInterest, 999}})) {
}
or
some_constructor(...) : myVector(make_vector<int>(3, {{indexOfInterest, 999}})) {
}
with the second overload.
Instead of an array, you can also take the argument as a std::initializer_list<...>
with the difference being that the array version instantiates new functions for each list size given to the call, while the initializer list will use the same function for different list sizes.
The function does in any case not copy the constructed vector. It may however move it once (C++17, due to mandatory copy elision except for vec
into the return value) or multiple times (before C++17). All these may however be optimized away by the compiler due to (named) return value optimization.
Upvotes: 0
Reputation: 122142
No there is no such thing in C++11. If it is only a single index that needs a different initializer than all the others, I wouldn't use anything other than
std::vector<int> myVector(3);
myVector[1] = 999;
I think it wouldn't be too difficult to write some fake iterator class that lets you write something along the line of
auto pit = pair_of_fake_iterators(3,1,999);
std::vector<int> myVector( pit.first, pit.second);
Which calls the constructor that takes two iterators and "copies" elements. Though, I wouldn't go there. It saves you nothing on typing and readers of your code will applaude for the level of obfuscation.
Upvotes: 2
Reputation: 29965
Another way of doing this would be to use std::unordered_map
. Not quite the same thing, but could work for you depending on what you're doing:
std::unordered_map<int, int> mp{ { indexOfInterest, 999} };
mp[someOtherIndex] = 42;
All the indexes you haven't assigned anything to will have the default value (0 in this case) when you try to access them.
Upvotes: 2
Reputation: 180500
As long as you want everything in the vector to be the same value except for the one value you want different, you can use
std::vector<int> myVector(size_of_vector, /* some_value_here_if_you_do_not_want_0 */);
myVector[index_you_care_about] = value;
Upvotes: 0