Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cleanly use: const char* and std::string?

tl:dr

How can I concatenate const char* with std::string, neatly and elegantly, without multiple function calls. Ideally in one function call and have the output be a const char*. Is this impossible, what is an optimum solution?

Initial Problem

The biggest barrier I have experienced with C++ so far is how it handles strings. In my opinion, of all the widely used languages, it handles strings the most poorly. I've seen other questions similar to this that either have an answer saying "use std::string" or simply point out that one of the options is going to be best for your situation.

However this is useless advice when trying to use strings dynamically like how they are used in other languages. I cannot guaranty to always be able to use std::string and for the times when I have to use const char* I hit the obvious wall of "it's constant, you can't concatenate it".

Every solution to any string manipulation problem I've seen in C++ requires repetitive multiple lines of code that only work well for that format of string. I want to be able to concatenate any set of characters with the + symbol or make use of a simple format() function just how I can in C# or Python. Why is there no easy option?

Current Situation

Standard Output

I'm writing a DLL and so far I've been output text to cout via the << operator. Everything has been going fine so far using simple char arrays in the form:

cout << "Hello world!"

Runtime Strings

Now it comes to the point where I want to construct a string at runtime and store it with a class, this class will hold a string that reports on some errors so that they can be picked up by other classes and maybe sent to cout later, the string will be set by the function SetReport(const char* report). So I really don't want to use more than one line for this so I go ahead and write something like:

SetReport("Failure in " + __FUNCTION__ + ": foobar was " + foobar + "\n"); // __FUNCTION__ gets the name of the current function, foobar is some variable

Immediately of course I get:

  • expression must have integral or unscoped enum type and...
  • '+': cannot add two pointers

Ugly Strings

Right. So I'm trying to add two or more const char*s together and this just isn't an option. So I find that the main suggestion here is to use std::string, sort of weird that typing "Hello world!" doesn't just give you one of those in the first place but let's give it a go:

SetReport(std::string("Failure in ") + std::string(__FUNCTION__) + std::string(": foobar was ") + std::to_string(foobar) + std::string("\n"));

Brilliant! It works! But look how ugly that is!! That's some of the ugliest code I've every seen. We can simplify to this:

SetReport(std::string("Failure in ") + __FUNCTION__ + ": foobar was " + std::to_string(foobar) + "\n");

Still possibly the worst way I've every encounter of getting to a simple one line string concatenation but everything should be fine now right?

Convert Back To Constant

Well no, if you're working on a DLL, something that I tend to do a lot because I like to unit test so I need my C++ code to be imported by the unit test library, you will find that when you try to set that report string to a member variable of a class as a std::string the compiler throws a warning saying:

warning C4251: class 'std::basic_string<_Elem,_Traits,_Alloc>' needs to have dll-interface to be used by clients of class'

The only real solution to this problem that I've found other than "ignore the warning"(bad practice!) is to use const char* for the member variable rather than std::string but this is not really a solution, because now you have to convert your ugly concatenated (but dynamic) string back to the const char array you need. But you can't just tag .c_str() on the end (even though why would you want to because this concatenation is becoming more ridiculous by the second?) you have to make sure that std::string doesn't clean up your newly constructed string and leave you with garbage. So you have to do this inside the function that receives the string:

const std::string constString = (input);
m_constChar = constString.c_str();

Which is insane. Because now I traipsed across several different types of string, made my code ugly, added more lines than should need and all just to stick some characters together. Why is this so hard?

Solution?

So what's the solution? I feel that I should be able to make a function that concatenates const char*s together but also handle other object types such as std::string, int or double, I feel strongly that this should be capable in one line, and yet I'm unable to find any examples of it being achieved. Should I be working with char* rather than the constant variant, even though I've read that you should never change the value of char* so how would this help?

Are there any experienced C++ programmers who have resolved this issue and are now comfortable with C++ strings, what is your solution? Is there no solution? Is it impossible?

like image 474
sydan Avatar asked Apr 29 '15 10:04

sydan


2 Answers

The standard way to build a string, formatting non-string types as strings, is a string stream

#include <sstream>

std::ostringstream ss;
ss << "Failure in " << __FUNCTION__ << ": foobar was " << foobar << "\n";
SetReport(ss.str());

If you do this often, you could write a variadic template to do that:

template <typename... Ts> std::string str(Ts&&...);
SetReport(str("Failure in ", __FUNCTION__, ": foobar was ", foobar, '\n'));

The implementation is left as an exercise for the reader.

In this particular case, string literals (including __FUNCTION__) can be concatenated by simply writing one after the other; and, assuming foobar is a std::string, that can be concatenated with string literals using +:

SetReport("Failure in " __FUNCTION__ ": foobar was " + foobar + "\n");

If foobar is a numeric type, you could use std::to_string(foobar) to convert it.

like image 106
Mike Seymour Avatar answered Sep 21 '22 05:09

Mike Seymour


Plain string literals (e.g. "abc" and __FUNCTION__) and char const* do not support concatenation. These are just plain C-style char const[] and char const*.

Solutions are to use some string formatting facilities or libraries, such as:

  • std::string and concatenation using +. May involve too many unnecessary allocations, unless operator+ employs expression templates.
  • std::snprintf. This one does not allocate buffers for you and not type safe, so people end up creating wrappers for it.
  • std::stringstream. Ubiquitous and standard but its syntax is at best awkward.
  • boost::format. Type safe but reportedly slow.
  • cppformat. Reportedly modern and fast.
like image 28
Maxim Egorushkin Avatar answered Sep 22 '22 05:09

Maxim Egorushkin