4

So for example, on GeeksForGeeks.org, contributing user "Kartik" offers the following example for initializing a vector of integers:

// CPP program to initialize a vector from 
// an array. 
#include <bits/stdc++.h> 
using namespace std; 

int main() 
{ 
    int arr[] = { 10, 20, 30 }; 
    int n = sizeof(arr) / sizeof(arr[0]); 

    vector<int> vect(arr, arr + n); 

    for (int x : vect) 
        cout << x << " "; 

    return 0; 
} 

If I understand what I'm reading correctly, sizeof(arr) is some number (which I assume is the length of the array arr; i.e. 3, please correct me if I'm wrong) divided by sizeof(arr[0]) (which I assume to be 1) -- basically just being a roundabout way of saying 3/1 = 3.

At this point, vector<int> vect(arr, arr + n) appears to be a vector of size 3, with all values initialized to arr + n (which I'm assuming is a way of saying "use the 3 items from arr to instantiate; again, please correct me if I'm wrong).

Through whatever sorcery, the output is 10 20 30.

Now, regardless of whether or not any of my above rambling is coherent or even remotely correct, my main question is this: can the same technique be used to instantiate some example vector<string> stringVector such that it would iterate through strings designated by some example string stringArray[] = { "wordA", "wordB", "wordC" }? Because, as I understand it, strings have no numeric values, so I imagine it would be difficult to just say vector<string> stringVector(stringArray, stringArray + n) without encountering some funky junk. So if it is possible, how would one go about doing it?

As a rider, why, or in what type of instance, would anyone want to do this for a vector? Does instantiating it from an array (which as I understand it has constant size) defeat the purpose of the vector?

Just as a disclaimer, I'm new to C++ and a lot of the object-oriented syntax involving stuff like std::vector<_Ty, _Alloc>::vector...etc. makes absolutely no sense to me, so I may need that explained in an answer.

To whoever reads this, thank you for taking the time. I hope you're having a good day!

NJJ_002
  • 53
  • 7
  • 1
    Remove using namespace std and the bits include. These things are bad. – Michael Chourdakis Jan 04 '21 at 22:55
  • 1
    @MichaelChourdakis That's excellent advice, but that program is directly copied from somewhere. Fixing it would alter the code, and since the question is about the original code, I don't think it should be removed in this case. – cigien Jan 04 '21 at 22:57
  • 3
    `sizeof(arr)` is the total size the array uses. As `arr` is an array of integers and an integer has size 4 (bytes), `sizeof(arr)` will be 12. Then, you divide it by `sizeof(arr[0])`, which is an integer, therefore has size 4. 12/4 = 3. – Daniel Jan 04 '21 at 22:57
  • 1
    Can you clarify from which C++ textbook did you learn all about "`#include `"? This is not a standard C++ header file, so you should throw this textbook away and get a better C++ textbook, which will hopefully explain why you should [completely forget that `using namespace std;` exists in C++](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice). – Sam Varshavchik Jan 04 '21 at 22:57
  • _so I imagine it would be difficult to just say..._ Did you try it? Because [it works](https://wandbox.org/permlink/w1v0kwLmICzME6fv). – Paul Sanders Jan 04 '21 at 23:00
  • 1
    `sizeof arr` is the number of bytes of memory occupied by `arr`. `sizeof (arr[0])` is number of bytes occupied by the first element of `arr`. Both values are implementation-defined - i.e. they vary between compilers. `sizeof (arr[0])` is equal to `sizeof(int)` which - with real-world compilers typically has values of `2` (a 16 bit `int`), `4` (a 32-bit `int`), or `8` (a 64-bit `int`) BUT other values are possible. `arr` is an array of 5 `int`, so it works out that `sizeof(arr)` is equal to `5` times `sizeof(int)`. – Peter Jan 04 '21 at 23:01
  • 6
    `vector vect(arr, arr + n);` could be `vector vect(begin(arr), end(arr));`. This makes computing the size of the array moot. [Documentation for `begin`](https://en.cppreference.com/w/cpp/iterator/begin). [Documentation for `end`](https://en.cppreference.com/w/cpp/iterator/end). – user4581301 Jan 04 '21 at 23:02
  • 4
    The reason you do something like this is because you've either been taught an old version of C++ or you were taught current C++ 10 or more years ago and haven't kept up to date. Since C++11 `vector vect{"abc","def","ghi"};` is viable. – user4581301 Jan 04 '21 at 23:15
  • `int n = std::size(arr)` also works in C++ 17 (for a C style array). – Phil1970 Jan 04 '21 at 23:50
  • @PaulSanders That's interesting, I only got errors trying it on my own; can you walk me through why it's working? – NJJ_002 Jan 05 '21 at 00:12
  • 1
    Well, as you probably know, `std::vector` has a constructor that takes two iterators as parameters and C++ treats pointers as iterators in this context. – Paul Sanders Jan 05 '21 at 12:02

5 Answers5

3

Clarifications:

  • sizeof(arr): returns the size in bytes of the array, which is 12 because it has 3 ints, and each int in most implementations has a size of 4 bytes, so 3 bytes x 4 = 12 bytes.
  • sizeof(arr[0]): returns the size in bytes of the first element of the array, which is 4 because it is an int array.
  • vector<int> vect(arr, arr + n): the vector class has multiple constructors. Here we are not using the constructor you are thinking of. We are using a constructor that takes begin and end iterators for a range of elements, making a copy of those elements. Pointers can be used as iterators, where in this case arr is the begin iterator and arr + n is the end iterator.

Note: int* + int returns int*.

Note: We should also consider that the "end" of an array is a pointer to the next space after the last item in the array, and the constructor will copy all the items except the item past the end.

link

Answer:

Yes, remember that here, the constructor is taking iterators, not any item of the array, so we can do it easily like this with little changes:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    // changed int to string and the array values
    string arr[] = { "one", "two", "three" };
    int n = sizeof(arr) / sizeof(arr[0]);

    // changed int to string
    vector<string> vect(arr, arr + n);
    
    // changed int to string
    for (string x : vect)
        cout << x << " ";
    return 0;
}
DonLarry
  • 702
  • 2
  • 11
  • 22
  • 3
    Re: “each `int` has a size of 4 bytes” — not necessarily. He standard requires that an `int` has a size of at least 2 bytes, and many older implementations did that. Some still do. And I’m sure there are implementations with 8-byte `int`s. The **calculation** works regardless of the size of `int`. – Pete Becker Jan 04 '21 at 23:58
2

Yes, you can do so - you just need to define something that the constructor for String will take (which is a 'const char')

   const char * arr[] = { "abc","def","ghi" };
    int n = sizeof(arr) / sizeof(arr[0]);

    vector<string> vect(arr, arr + n);

    for (string &x : vect)
        cout << x << " ";

What this is effectively doing is creating the vector from two iterators (a pointer is, loosely, an iterator):

https://en.cppreference.com/w/cpp/container/vector/vector

Constructs the container with the contents of the range [first, last).
This constructor has the same effect as vector(static_cast<size_type>(first), static_cast<value_type>(last), a) if InputIt is an integral type.

And as @MartinYork pointed out, it's much more readable to use the C++ syntax:

    const char * arr[] = { "abc","def","ghi" };
    vector<string> vect(std::begin(arr), std::end(arr)); 
Halt State
  • 421
  • 2
  • 6
  • 3
    This is C++ use `std::begin()` and `std::end()` to get iterators for the array `arr`. Its much more readable. – Martin York Jan 04 '21 at 23:07
  • 1
    *a pointer is, loosely, an iterator*: A pointer implements the concept `Iterator`. – Martin York Jan 04 '21 at 23:09
  • 4
    And the ultimate short-cut: `vector vect{"abc","def","ghi"};`. No need for the array at all. The tutorial the asker is working from is about 10 years out of date. – user4581301 Jan 04 '21 at 23:10
  • 2
    @user4581301 I suggest you to write an answer explaining the differences between constructing a vector before and after c++11 (std::initializer_list) as the asker also asks about **why** would someone do this for a vector. – yemre Jan 04 '21 at 23:22
  • @user4581301 I'm going to agree with yemre here on this one; it's cool that it's outdated -- that part's easy enough to accept -- but why is it outdated, and still, why would you do this for a vector in the first place? The shortcut is also useful and probably great information, but does the shortcut always work, or should someone know how to do it without the shortcut to be able to say that they know how to do it? – NJJ_002 Jan 05 '21 at 00:18
2

So if it is possible, how would one go about doing it?


Simply use vector constructor number 5, which accepts iterators to start and end of range

  1. Constructs the container with the contents of the range [first, last).
#include <iostream>
#include <vector>
#include <string>

int main()
{
    std::string arr[] = { "wordA", "wordB", "wordC" };    
    std::vector<std::string> v {std::begin(arr), std::end(arr)};

    for (auto& str : v)
        std::cout << str << "\n";
    return 0;
}
Tony Tannous
  • 14,154
  • 10
  • 50
  • 86
  • I think that this is probably all fine and well, but I don't necessarily understand it or why it works because of the syntax. I'm just starting out and I have no idea what's going on with the colon notation (`std::blahblah`) or the "v" since there isn't a variable `v` declared anywhere. And is the ampersand supposed to indicate a pointer? – NJJ_002 Jan 05 '21 at 00:10
  • @NicholasJarecki that line is creating `v` using constructor number 5 in the link I added. & is for reference in this case. – Tony Tannous Jan 05 '21 at 00:29
2

sizeof(arr)

sizeof gets the size of an object in bytes. The size of an object is the total number of bytes required by the object. Note that I'm using "object" in the C++ context, not the OOP context (an instance of a class).

The size of an object of a given type is always the same. A std::string containing "a" is the same size as a string containing the unabridged text of War and Peace. Any object that appears to have a variable size really contains a reference to variable length data stored elsewhere. In the case of std::string at its most basic, it is a pointer to a dynamically allocated array and an integer keeping track of how much of the dynamically allocated array is actually in use by the string. std::vector is similar, typically it's a pointer to the start of its data, a pointer to the end of its data, and a pointer to the first empty position in the data. No matter how big the vector is, sizeof(vector) will return the size of the pointers, any other book-keeping variables in the vector implementation, and any padding needed to guarantee correct memory alignment.

This means every item in an array is always the same size and thus the same distance from one another.

Through whatever sorcery...

The above means that the total size of the array divided by the size of one element in the array, sizeof(arr) / sizeof(arr[0]), will always provide the number of elements in the array. It doesn't matter what the array contains, numerical or otherwise. There are of course prettier ways like

template <class T, size_t N>
size_t getsize (const T (&array)[N])
{
    return N;
}

and later

size_t n = getsize(arr);

As a rider, why, or in what type of instance, would anyone want to do this for a vector?

In the old days one could not directly construct a vector pre-loaded with data. No one wants to write some arbitrary number of lines of push_back to pound all the values in manually, It's boring as hell, a programmer almost always has better things to do, and the odds of injecting an error are too high. But you could nicely and easily format an array and feed the array into the vector, if you needed a vector at all. A lot of the time you could live off the array by itself because the contents were unchanging or at worst would only be shuffled around.

But if the number of contents could change, it could be time for a vector. If you're going to add items and you don't know the upper limit, it's time for vector. If you're calling into an API that requires a vector, it's time for a vector.

I can't speak for everybody, but I'm going to assume that like me a lot of people would have loved to have that easy-peasy array-style initialization for vectors, lists, maps, and the rest of the usual gang.

We were forced to write programs that generated the appropriate code to fill up the vector or define an array and copy the array into the vector much like the above example.

In C++11 we got our wish with std::initialzer_list and a variety of new initialization options1 that allowed

vector<string> vect{"abc","def","ghi"};

eliminating most cases where you would find yourself copying an array into a library container. And the masses rejoiced.

This coincided with a number of tools like std::size, std::begin and std::end to make converting an array into a vector a cakewalk. Assuming you don't pass the array into a function first.

1 Unfortunately the list of initialization options can get a lil' bewildering

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • Thank you for your explanations! I think you said it, but I'd just like to clarify: are the colons e.g. `std::string` supposed to indicate pointers? And if you don't include the `using namespace std;` bit, how does the compiler (or whatever is doing the thing) know that you're referencing that same `std`? Is it a native function of the language or is it packaged with an `#include <>`? – NJJ_002 Jan 05 '21 at 00:26
  • 2
    "*are the colons e.g. `std::string` supposed to indicate pointers?*" - no. They denote scopes/namespaces. `std::string` is an alias for `std::basic_string`. `basic_string` is a (template) class in the `std` namespace. "*And if you don't include the `using namespace std;` bit, how does the compiler (or whatever is doing the thing) know that you're referencing that same `std`?*" - there is only 1 `std` namespace, and you would have to include it explicitly in your declarations, eg `std::vector` instead of `vector`. – Remy Lebeau Jan 05 '21 at 00:30
  • 1
    @NicholasJarecki The colons are the scope resolution operator. They tell the compiler that `string` is part of the `std` namespace. `using namespace std;` doesn't allow you to use stuff in the `std` namespace, that comes free of charge by including the correct headers. `using namespace std` takes whatever's in the `std` namespace and put it into the global namespace where it can get in the way of your code. Leads to stupid problems like this: https://godbolt.org/z/j8EG3j the code works great and then you upgrade the compiler and find out that suddenly you have two things named `size` – user4581301 Jan 05 '21 at 00:32
0

Here's how you'd do it. Note that it's a tad awkward to get the length of the array, but that's just because arrays don't carry that information around with them (use a vector!).

#include<string>
#include<vector>
#include<iterator>
#include<iostream>


int main()
{
    std::string arr[] = {"abc", "def", "ghi"};
    std::vector<std::string> tmp;
    std::copy(arr, arr + sizeof(arr)/sizeof(arr[0]), std::back_inserter(tmp));
    for(auto str : tmp) {
        std::cout<<str<<"\n";
    }
}

Update: Yes good point about using std::begin and std::end for the array.

Tumbleweed53
  • 1,491
  • 7
  • 13