148

Short of (the obvious) building a C style string first then using that to create a std::string, is there a quicker/alternative/"better" way to initialize a string from a vector of chars?

1
  • 6
    Can this be done without copying? In other words something which does the same thing as std::vector<char> v2(std::move(v)) but with a std::string as the new object. Commented Oct 2, 2018 at 3:17

10 Answers 10

243

Well, the best way is to use the following constructor:

template<class InputIterator> string (InputIterator begin, InputIterator end);

which would lead to something like:

std::vector<char> v;
std::string str(v.begin(), v.end());
Sign up to request clarification or add additional context in comments.

2 Comments

This really would be the best way and is better than using .data() approaches, since you are not sure the vector<char> is necessarily null terminated
This only works if the vector is not null-terminated, or if you want your std::string to have null characters.
52

I think you can just do

std::string s( MyVector.begin(), MyVector.end() );

where MyVector is your std::vector.

1 Comment

Except in VS2013 which asserts at runtime about invalid iterators, unless you set _ITERATOR_DEBUG_LEVEL=1 (in which case it seems to work fine).
48

You can do std::string(v.data(), v.size()), or if you're entirely certain your vector is null terminated, you can simply use std::string(v.data())

5 Comments

Even with C++98, I believe you can do std::string(&v[0]). This, of course, is if the vector is null-terminated.
@Jamie: By the way, in C++98, string(&v[0], v.size()) should work also, but only after assert(not v.empty());, since if the vector is empty, both v[0] and v.front() would invoke undefined behavior. That, aside from the syntactic simplicity of not having to use the address-of operator, is the real benefit of C++11's data() function, which works even on an empty vector.
Very true. Unfortunately, my project is stuck on Visual Studio 2008 for the foreseeable future. Right about checking vector length first.
If vector does not contain a '\0', std::string(v.data()) might lead a longer string. So do not use this way.
@heLomaN: What's wrong with std::string(v.data(), v.size()), which was explicitly mentioned in the answer for that exact reason?
13
std::string s(v.begin(), v.end());

Where v is pretty much anything iterable. (Specifically begin() and end() must return InputIterators.)

Comments

10

I like Stefan’s answer (Sep 11 ’13) but would like to make it a bit stronger:

If the vector ends with a null terminator, you should not use (v.begin(), v.end()): you should use v.data() (or &v[0] for those prior to C++17).

If v does not have a null terminator, you should use (v.begin(), v.end()).

If you use begin() and end() and the vector does have a terminating zero, you’ll end up with a string "abc\0" for example, that is of length 4, but should really be only "abc".

2 Comments

Very good point on the null terminator, I fell into that trap.
Is there a performance difference between (v.begin(), v.end()) and (v.data())? Will the latter be O(n)?
5

Just for completeness, another way is std::string(&v[0]) (although you need to ensure your string is null-terminated and std::string(v.data()) is generally to be preferred.

The difference is that you can use the former technique to pass the vector to functions that want to modify the buffer, which you cannot do with .data().

4 Comments

Why can't you use data() to modify the buffer? It looks to me like if you call data() on a non-const vector, it will return a T * (and if your vector is const, 'v[0]` would return a T const & anyway).
@AdamH.Peterson it seems to be an oversight in the standard. Std::vector's data has both const and non-const functions, whereas in the current standard, std::string has only a const function: en.cppreference.com/w/cpp/string/basic_string/data Note that C++17 adds a non-const version, so this may not be the case indefinitely - however, the current standard states "Modifying the character array accessed through data has undefined behavior."
That would be true if v were a string, but the target type is a string and v is a vector. (But it is nice to see that C++17 will give strings parity with vector.)
@AdamH.Peterson you're right of course. The question was answered a while ago, I'd assumed the whole question was about strings exclusively without checking.
2

Just for code demonstration:

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

int main() {

    std::vector<char> v1 {'l', 'o', 'v', 'e', '\0', '\0', '\0'};
    std::vector<char> v2 {'l', 'o', 'v', 'e', '\0', '\0', '\0', 'y', 'o', 'u'};    

    for (auto & v : {v1, v2}){
        std::string s1(v.cbegin(), v.cend());   
        std::cout << s1 << "|---" << s1.size() << '\n';    

        std::string s2(v.data());
        std::cout << s2 << "|---" << s2.size() << '\n';        

        std::string s3(&v[0]);
        std::cout << s3 << "|---" << s3.size() << '\n';

        std::string s4(v.data(), v.size());
        std::cout << s4 << "|---" << s4.size() << '\n';

        std::string s5(&v[0], v.size());
        std::cout << s5 << "|---" << s5.size() << '\n';

        std::cout << '\n';
    }    
}

Output:

love|---7
love|---4
love|---4
love|---7
love|---7

loveyou|---10
love|---4
love|---4
loveyou|---10
loveyou|---10

Comments

1
vector<char> vec;
//fill the vector;
std::string s(vec.begin(), vec.end());

5 Comments

Could you add some explanations as well as the code?
C++11 string library template goes as follows default (1) string(); copy (2) string (const string& str); substring (3) string (const string& str, size_t pos, size_t len = npos); from c-string (4) string (const char* s); from buffer (5) string (const char* s, size_t n); fill (6) string (size_t n, char c); range (7) template <class InputIterator> string (InputIterator first, InputIterator last); initializer list (8) string (initializer_list<char> il); move (9) string (string&& str) noexcept; we are following 7th template.
Hi @TechCat! Please read up on writing a good answer before answering your next question. Enjoy your stay at SO!
Could you edit your answer with the description please?
Not sure why this was down voted, this is a perfectly acceptable answer.
0

Interesting that the initializer list has not been mentioned yet

std::string s{v.begin(), v.end()}

According to Nicolai Josuttis

The brace initialization has the following advantages:

•It can be used with fundamental types, class types, aggregates, enumeration types, and auto

• It can be used to initialize containers with multiple values

• It can detect narrowing errors (e.g., initialization of an int by a floating-point value)

• It cannot be confused with function declarations or calls

• If the braces are empty, the default constructors of (sub)objects are called and fundamental data types are guaranteed to be initialized with 0 / false / nullptr

Comments

0

To bring this up to C++23, use ranges::to<>(), which will also handle any range (container). It also handles embedded zeros.

#include <ranges>
#include <string>
#include <vector>

namespace rng = std::ranges;
namespace vws = std::views;

#include <fmt/ranges.h>
using fmt::println, fmt::print;

auto main(int, char**) -> int {
   std::vector<char> v1 {'l', 'o', 'v', 'e', '\0', '\0', '\0'};
   std::string s1 = v1 | rng::to<std::string>();
   println("{}", s1);

   std::vector<char> v2 {'l', 'o', 'v', 'e', '\0', '\0', '\0', 'y', 'o', 'u'};
   std::string s2 = v2 | rng::to<std::string>();
   println("{}", s2);

   return 0;
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.