string constructor taking two char* into another std::string works in c++14 but not c++17
Asked Answered
B

2

18

The following program attempts to construct a second string using the first string and a pointer into the middle of the first string:

#include <string>

int main() {
  std::string src = "hello world";
  const char* end = &src[5];
  std::string dest(src.data(), end);
}

In C++14 and earlier this works. But in C++17 the call fails:

error: no matching function for call to ‘std::__cxx11::basic_string<char>::basic_string(char*, const char*&)’
   std::string dest(src.data(), end);
[... full output omitted ...]

What changed to make this fail?

Bombast answered 16/1, 2018 at 19:54 Comment(0)
B
34

The construction of dest is attempting to use the following constructor (from cppreference):

template< class InputIt >
basic_string( InputIt first, InputIt last, 
              const Allocator& alloc = Allocator() );

This requires that first and last have the exact same type. The problem is that in C++17 std::string::data returns a non-const pointer when called on a non-const std::string. This means the type of first is char* and the type of last is const char*. Since they differ, the template argument InputIt cannot be deduced and the call fails.

There is no way to explicitly specify the template argument to a constructor call, but there is a solution. std::string::c_str still returns a const char* and the construction of dest can use it:

std::string dest(src.c_str(), end);

Another solution would be calling data() on a str through a const reference:

const auto& const_src = src;
std::string dest(const_src.data(), end);

Or if you don't care about C++14 compatability you can use std::as_const (thanks Mário Feroldi)

std::string dest(std::as_const(src).data(), end);
Bombast answered 16/1, 2018 at 19:54 Comment(11)
Another possibility is to just get rid of the end pointer altogether: const char* start = src.c_str(); std::string dest(start, start+5); Or: char* start = src.data(); std::string dest(start, start+5); Or, just use a different constructor, like: std::string dest(src.c_str(), 5);Boletus
You can use C++17's std::as_const to call the const version of data().Sclerous
I think proper solution is to define end as an iterator auto end = std::next( src.begin(), 5 ); and not to use pesky pointersGhat
@zett42 not in this case, as there is std::string::iterator and it is not equal to pointerGhat
also std::string dest(&src[0], end); -- matching the same method of generating iterator for both argumentsAcquire
@Ghat A pointer is always an iterator, that doesn't change on case-by-case basis. In OPs question, the only issue is that the type of begin and end iterator is different (const vs. non-const).Waterline
@Waterline I do not want to argue, but if OP would use std::begin() + 5 and std::begin() code would work c++11 and later.Ghat
@Ghat Sure, as would std::string::data() + 5 and std::string::data().Waterline
If the code is to be converted to C++17 anyway, then the OP might consider turning dest into a std::string_view.Comfortable
Are we just listing alternatives? Count me in src.substr(0, 5)Keeley
@Keeley std::string dest{"hello"}; and you don't even need a src!Bombast
D
0

You probably should just tap into the 4th ctor string( const char* s, size_type count ) (14th ctor in VS)

pointer diff

and write something like:

std::string dest(src.data(), end - src.c_str());
Disadvantaged answered 16/4 at 4:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.