2

I'm trying to transform a vector of strings to a vector of pairs of strings, and I was getting segfault. I've tried to narrow it down to a simple test case (below), and I'm sure it's likely to do with the memory allocation:

#include <string>
#include <vector>
#include <utility>

std::pair<std::string, std::string>
newP( const std::string& foo) {
  return std::make_pair(std::string("Foo"), std::string("Bar"));
}

int main()
{
  std::vector<std::string> vec;
  std::vector<std::pair<std::string, std::string>> pairs;

  vec.push_back("Hello, ");
  vec.push_back("World!");

  std::transform(vec.cbegin(), vec.cend(), pairs.begin(), newP);
}

I get following segfault on FreeBSD 13.1:

Program received signal SIGSEGV, Segmentation fault.
Address not mapped to object.
0x0000000000205fd5 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__is_long (this=0x0) at /usr/include/c++/v1/string:1456
1456            {return bool(__r_.first().__s.__size_ & __short_mask);}
(gdb) bt
#0  0x0000000000205fd5 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__is_long (this=0x0) at /usr/include/c++/v1/string:1456
#1  0x0000000000205f0d in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__move_assign (this=0x0, __str="Foo") at /usr/include/c++/v1/string:2463
#2  0x0000000000205ee1 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::operator= (this=0x0, __str="Foo") at /usr/include/c++/v1/string:2485
#3  0x0000000000205ded in std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::operator= (
    this=0x0, __p=...) at /usr/include/c++/v1/__utility/pair.h:277
#4  0x0000000000203fe5 in std::__1::transform<std::__1::__wrap_iter<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const*>, std::__1::__wrap_iter<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >*>, std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > (*)(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> (__first=..., __last=...,
    __result=..., __op=0x203c20 <newP(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)>) at /usr/include/c++/v1/__algorithm/transform.h:29
#5  0x0000000000203d96 in main () at pair.cc:19

Could someone please explain what I'm doing wrong ? And if possible, what would be the best way to fix this. I'm fairly out of touch with modern C++.

Thanks!

abbe
  • 123
  • 4
  • 5
    Does it work if you pass `std::back_inserter(pairs)` to `std::transform` rather than `pairs.begin()`? The iterator from `pairs.begin()` doesn't know how to make `pairs` bigger to fit the new elements, but a `back_insert_iterator` does. – Wander Nauta Sep 05 '22 at 07:28
  • 1
    That works too (in addition to the answer). Thanks! – abbe Sep 05 '22 at 07:32

1 Answers1

7

The vector pairs is empty, and pairs.begin() will return the pairs.end() iterator which can't be dereferenced.

Set the size of pairs to the same size as vec before calling transform:

pairs.resize(vec.size());
std::transform(vec.cbegin(), vec.cend(), pairs.begin(), newP);

An alternative that has been mentioned is to preallocate memory and then use std::back_inserter to append to the vector:

pairs.reserve(vec.size());
std::transform(vec.cbegin(), vec.cend(), std::back_inserter(pairs), newP);

While the reserve call isn't strictly needed, it will avoid extra re-allocations and copying that might otherwise happen when adding new elements to the vector.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 1
    This answer lacks the back_inserter option. – Mehno Sep 05 '22 at 07:35
  • 3
    @Mehno an answer does not have to include all possible solutions in order to be accepted or upvoted. And specifically regarding this question the `resize` option is actually better than using the `back_inserter` (since we know the size in advance) due to possible reallocations of the vector when using `back_inserter`. – wohlstad Sep 05 '22 at 07:38
  • 1
    @wohlstad `reserve` (and then using a back inserter) is sufficient to avoid reallocations – 463035818_is_not_an_ai Sep 05 '22 at 07:50
  • @wohlstad you are wrong in multiple ways. I did not comment to criticise but to improve. The comment is for someone passing by to state that there are alternative ways. Also, in my opinnion, reserving + back_inserter will safe you container size many default constructor and destructor calls. – Mehno Sep 05 '22 at 07:55
  • 1
    @Mehno then I misjudged your comment, sorry. And I agree about `reserve`. – wohlstad Sep 05 '22 at 08:10