Jump to content
  • Advertisement
Sign in to follow this  
Juliean

string_view in functions that require a c-string?

Recommended Posts

Hi,

I've recently starting porting my codebase towards using std::string_view, which is part of the new C++17-standard. I've actually implemented my own version of it, which might be important later when talking about the problem I now face.

So at first it seemed really straight-forward, just replace pretty much every occurance of "const std::string&" with "sys:StringView", except for places where I can take advantage of move-semantics. But then I ran into issues, at places where functions from the standard c-library/WinAPI were being called, like this:

 void copy(sys::StringView stInName, sys::StringView stOutName)
 {
   std::ifstream ifs(stInName.Data(), std::ios::binary);
   std::ofstream ofs(stOutName.Data(), std::ios::binary);

   if(!ifs.is_open())
     throw fileException();
   else if(!ofs.is_open())
     throw fileException();

   ofs << ifs.rdbuf();
 }

Oups, std::ifstream expects a null-terminated c-string, and sys::StringView can (and often will) point to a portion of a char-array that is not null-terminated, ie. if a Substr() is generated from another stringview. This obviously means subtle fails when trying to open a file, convert a string to an int, etc... as the function will ignore the size stored in the string-view, and will continue to read characters until the end of the actual string.

Now my question to those who have already used std::string_view, or just had a thorough thought about it: How would you solve this situation? The premise is to use std::string_view as much as possible due to the inherent benefits not only for speed but also for clearity. The ideas I so far had:

- Just pass "const std::string&" to the functions expecting a c-string. Pretty messy, as most other code uses or generates StringViews, which means I have to manually convert to std::string whenever I call such an function.

- Before a call that expects a c-string, convert StringView to std::string and use that. While it technically shouldn't matter as most of the calls requiring a c-string are expensive by themselves, it kind of defeats the purpose of using the StringView in the first place, and actually make some functions slower then if I were just passing a "const std::string&".

- Make a "CStringView" class the always has to point to a null-terminated string. Would be the same as StringView, except it can only be created from an std::string or const char* directy. This would then be used for functions that require a c-string. I've tried it a bit but ran into problems with functions like the one above that are called from many different places with different data, especially when being used in my data-driven content pipeline, since its not trivial to make sure that when I call the function, I actually have a null-terminated string anymore.

- Make an "ToAPI"-function, which looks like this:

template<size_t Size>
std::array<Type, Size> _ToAPI(void) const
{
	std::array<Type, Size> vArray;

	CopyInto(vArray.data(), Size);
	vArray[Size()] = '\0';
  
	return vArray;
}

This actually saves the dynamic allocations made by converting to a string (and hopefully the array would benefit from copy-ellision/RVO), though it cannot be used with every type of string, since you have to specific the max-size beforehands. This is actually the solution I went with right now, as it allows me to hide the requirement for a real c-string on the callees side, should have a neglectible performance overhead and be somewhat safe (I can add checks that the string doesn't surpass the specified size). In addition, I'm rewriting most simpler functions that usually require a c-string (atoi, _strtoi64, ...) so they can work directly with my StringView-class.

 

Though I'm still not 100% happy, and wondering if there are any other options. So what did/would you do? Any one or combination fo the above; or something entirely different? Seeing how string_view is actually designed without a guarantee for 100% safety, I'm still "shocked" at how difficult things can get when interacting with "outdated" APIs...

Share this post


Link to post
Share on other sites
Advertisement

Looks to me like the only reasonable approach is to convert to std::string when you need to hit a C-style API. They want a fundamentally different type to the one that a string view provides and that is the end of it really. The string view is still useful for all the times when you don't reach an old-style API. Obviously if I then found specific parts of my code where this was not fast enough, I might then consider the std::array solution or similar.

Of course, if most of your code paths do end up wanting a null-delimited char* in the end, the string view is probably the wrong approach for this project.

Share this post


Link to post
Share on other sites
35 minutes ago, Kylotan said:

Looks to me like the only reasonable approach is to convert to std::string when you need to hit a C-style API. They want a fundamentally different type to the one that a string view provides and that is the end of it really. The string view is still useful for all the times when you don't reach an old-style API. Obviously if I then found specific parts of my code where this was not fast enough, I might then consider the std::array solution or similar.

Hm, yeah, makes sense to me I suppose. Speed shouldn't really shouldn't be an issue for all the code paths where I need a c-string, as those functions are usually expensive by themselves. I'm just a bit bummed that I ran into issues like that. There's a lot of other little inconveniences too, like heterogenous lookup in unordered containers and so on.

I just hope that at least the STL will be updated to make better use of string-views/strings with known length, I mean after all std::string_view is being implemented so why not fully make use of it?

40 minutes ago, Kylotan said:

Of course, if most of your code paths do end up wanting a null-delimited char* in the end, the string view is probably the wrong approach for this project.

Nah, that shouldn't be the case. Its mostly in my file IO & string-conversion wrappers, where the issue is rather that I just cannot reason about when and how a function like "copyFile" is being called. Other than that, using string view has paid of so far - except for the fact that it took 4 days so far integrating it in the base systems w/o being able to compile - good thing I'm not on a time-constraint or anything. :D

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!