How do I wrap a plain buffer of char's with an std::string or an equivalent thereof?
Asked Answered
C

4

6

Motivation:

I'm using some library (which I don't control) with the following function:

char* foo(/*some parameters here */);

Which returns a very long, null-terminated string. Now, I want to use the function

void bar(const std::string& s);

which is available in another library I'm using (that is, I don't control the signatures of either of these functions). I want to pass the result of foo() to bar() - without copying it.

Question:

I want to wrap a contiguous buffer of characters with a string object, so it can be passed as a const std::string&. How can I do this?

Notes:

  • I realize this may be frowned upon, but I'd like to do it anyway, if it's possible.
  • Yes, this may be inappropriate, or ugly, or too C-like - but it's what I (think I) need to do.
  • Comments on answers suggest that inheriting std::string is not an option, as it doesn't have the appropriate virtual methods. That basically leaves us with assuming things about the implementation (or writing different code for different implementations), and 'hand-constructing' an std::string instance. Thus, the question seems to be about doing that.
Cleome answered 3/5, 2014 at 14:29 Comment(3)
@MatthieuM.: I don't mind it owning its buffer (if it's const). I just need to construct it with an existing buffer.Cleome
Well, it can use an existing buffer (with a dedicated allocator) but the first step in building the string is copying from the source into the buffer given by the allocator. And I would advise against copying from the same buffer than the allocator is providing becausestd::char_traits<char>::copy which explicitly states it's undefined if source and destination overlap.Oftentimes
@MatthieuM.: Would this not be a futile attempt, anyway? After all, with a different allocator, it becomes a different type and would become incompatible with any interface requiring a std::string.Aesthetics
A
4

No, you can't. Review your requirements and eliminate the need for this.

Aesthetics answered 3/5, 2014 at 14:31 Comment(14)
You can do the opposite though. Allocate your buffer using a std::string and get it's char* pointer with s.c_str() and just use it.Photooffset
@Havenard: that's not quite right: c_str() gives back a char const* => you are NOT supposed to modify the underlying buffer!Oftentimes
Well, surely I can, somehow. That is, if I construct a string and change its internals directly (i.e. not using methods and such) then obviously I can force it to use the buffer I like. But I'm sure there's a nicer and more idiomatic way.Cleome
@Cleome that's precisely the part that you cannot do. well, you could if you implemented your own standard library, but it wouldn't be standards conforming anymore.Vowelize
@sehe: The standard library's string class must use a buffer somewhere, right? What prevents me from replacing the pointer to it?Cleome
It's so-called undefined behaviour, i.e. C++ does not specify what will happen if you attempt to do so. It's like dereferencing a null pointer or writing something into a random memory location.Aesthetics
It's more like what happens when you replace non-user servicable parts of your car. You loose the warranty. It might not be safe. Also, the pointers could be tampered with, but the invariants held by the devs implementing that class will likely be broken. So, yes, it's very very likely to ---kill kittens--- break.Vowelize
@einpoklum: By the way, std::string's implementation may be more complicated than you imagine. For example, MSVC uses "small-string optimisation", internally using a union to distinguish between large strings and small strings, the latter being stored directly in the objects to avoid dynamic allocation.Aesthetics
So, you're saying the implementation is not standardized and I can't make assumptions about it. And I can't, say, inherit std::string and do things whichever way I want?Cleome
Right, the implementation is not standardised. And inheriting won't help you at all. The implementation is private, not protected. The best you can do is hack together a solution which works with your implementation on your system, test it sufficiently and don't assume it will work somewhere else or that it will always work reliably on your system.Aesthetics
Do &s[0] to get the underlying buffer as non-constBrewery
@MattMcNabb: not reliably in pre-C++11.Aesthetics
There were no implementations that didn't do this reliably , you could say that every C++ compiler compiles with C++11 on this point :)Brewery
And yet it does not solve the OP's problem. He wants the opposite conversion.Aesthetics
A
5

Old question, but as of C++17, you now have std::string_view, which is precisely the right tool for the job.

Simply initialise it as:

auto my_string_view = std::string_view(foo(whatever));

...and use it anywhere you'd use a std::string const. Or if you need to make a non-const copy, you can simply initialise a temporary string from it with:

auto my_new_string = std::string{my_string_view}
Agglutination answered 1/3, 2017 at 15:0 Comment(2)
Yeah, I guess that's what I'd do these days. Of course, not everything supporting std::string_view's yet...Cleome
indeed; but in many compilers it's currently available as std::experimental::string_view in #include <experimental/string_view>Agglutination
A
4

No, you can't. Review your requirements and eliminate the need for this.

Aesthetics answered 3/5, 2014 at 14:31 Comment(14)
You can do the opposite though. Allocate your buffer using a std::string and get it's char* pointer with s.c_str() and just use it.Photooffset
@Havenard: that's not quite right: c_str() gives back a char const* => you are NOT supposed to modify the underlying buffer!Oftentimes
Well, surely I can, somehow. That is, if I construct a string and change its internals directly (i.e. not using methods and such) then obviously I can force it to use the buffer I like. But I'm sure there's a nicer and more idiomatic way.Cleome
@Cleome that's precisely the part that you cannot do. well, you could if you implemented your own standard library, but it wouldn't be standards conforming anymore.Vowelize
@sehe: The standard library's string class must use a buffer somewhere, right? What prevents me from replacing the pointer to it?Cleome
It's so-called undefined behaviour, i.e. C++ does not specify what will happen if you attempt to do so. It's like dereferencing a null pointer or writing something into a random memory location.Aesthetics
It's more like what happens when you replace non-user servicable parts of your car. You loose the warranty. It might not be safe. Also, the pointers could be tampered with, but the invariants held by the devs implementing that class will likely be broken. So, yes, it's very very likely to ---kill kittens--- break.Vowelize
@einpoklum: By the way, std::string's implementation may be more complicated than you imagine. For example, MSVC uses "small-string optimisation", internally using a union to distinguish between large strings and small strings, the latter being stored directly in the objects to avoid dynamic allocation.Aesthetics
So, you're saying the implementation is not standardized and I can't make assumptions about it. And I can't, say, inherit std::string and do things whichever way I want?Cleome
Right, the implementation is not standardised. And inheriting won't help you at all. The implementation is private, not protected. The best you can do is hack together a solution which works with your implementation on your system, test it sufficiently and don't assume it will work somewhere else or that it will always work reliably on your system.Aesthetics
Do &s[0] to get the underlying buffer as non-constBrewery
@MattMcNabb: not reliably in pre-C++11.Aesthetics
There were no implementations that didn't do this reliably , you could say that every C++ compiler compiles with C++11 on this point :)Brewery
And yet it does not solve the OP's problem. He wants the opposite conversion.Aesthetics
V
4

You can emulate it using boost::string_ref.

It has an interface that is interoperable with std::string:

boost::string_ref s = "hello world";

std::string compareto = "bye world";


bool test = (compareto != s);

Of course this will work only if you can opt to accept string_ref instead of string in your interfaces. That way, you can just pass c-strings or std::strings at leisure and it would do the right thing.

Vowelize answered 3/5, 2014 at 15:0 Comment(4)
... problem is, I'm using other people's / libraries' interfaces, so, essentially, no I can't take string_refs.Cleome
@einpoklum: an API which takes a std::string implicitly says that it expects all invariants of the string to be respected. Have you thought about the disasters which will happen if the string is internally copied with reference-counting or if small-string optimisation (SSO) is applied? If you pass a std::string to an API with the intention of messing around with its internal buffer later on, then you are using the library incorrectly. By the way, even if the API took a char const*, chances are it would copy the contents internally.Aesthetics
@Cleome Well then. Stop looking: you need std::string, and something else can't possibly work. End of story. Just make it so you read the buffers directly into std::string. Legally. E.g. using std::stringstreamVowelize
@ChristianHackl: But that's just implicit. I mean, when you write a C++ function of method taking a const std::string, you most often need it to meet very limited requirements.Cleome
B
0

You can't do this, but you can easily just write your own string class. Since you are explicitly disallowing making any copies of it, you don't even need to support many of std::string-type operations, such as concatenation, etc.

(In theory, you could support concatenation without copying by creating an internal tree of buffers, but that would be more work.)

Breastbone answered 3/5, 2014 at 15:12 Comment(13)
No offense, but - my own string class will not be usable with the code written by, well, everybody else in the world.Cleome
But if you want to impose a restriction different from std::string, well, then, you can't use std::string. Simple as that. Note that you can support interoperability/compatibility with std::string fine, though.Breastbone
Wrapping an arbitrary buffer with a const std::string should be interoperable with everything - at least, that's what my intuition tells me.Cleome
@einpoklum: No offense from my part either, but your intution is too low-level and C-centric. In C++, it is, well, wrong.Aesthetics
@einpoklum: The problem is that you can't make std::string wrap something in a way that it's not designed to do because, well, you can't modify std::string.Breastbone
@user3521733: Can't I inherit it?Cleome
The base class doesn't have virtual functions, so that won't help you with interfacing to the rest of the worldBrewery
Also, even if the public interface was virtual, you'd still have to deal with another std::string trying to access the internals of your std::string. Please try to do this, because you'll learn a lot in the process.Breastbone
@MattMcNabb: At this point I'm left cursing the standard library's designers. This is incredibly frustrating.Cleome
@Cleome what you're asking just isn't possible. Say you went char x[5]; string_wrapper s(x); where s is supposed to operate on that char array. What should happen when you add more than 5 characters to the string?Brewery
@MattMcNabb: This won't happen, since I'm passing my string wrapper to a function taking a const std::string&.Cleome
You will have to either copy the data or change that function.Brewery
@MattMcNabb: Or switch programming languages.Cleome

© 2022 - 2024 — McMap. All rights reserved.