operator const char* return

Pages: 12
Hi y'all i was wondering if i can have something like this on one line?

return (ss.str().c_str());
or
return ((ss.str()).c_str());

1
2
3
4
5
6
7
	operator const char* ()
	{
		ostringstream ss;
		ss << Name << " " << MiddleInitial << ". " << LastName << endl;
		FullName = ss.str();
		return FullName.c_str();
	}
Last edited on
> return (ss.str().c_str());
No - ss goes out of scope as soon as the function ends, and your char* pointer is off pointing to somewhere in the weeds.

Assuming FullName is a member of your class, this would be OK in some limited cases.

But you're still hosed if
- the class dtor gets called
- some other method decides to modify FullName

The only time you should really be calling .c_str() is at the point of calling some legacy / OS / library interface that needs a const char * parameter. Your bulk C++ code should stick to std::string
You can, I suppose, do this:
return (FullName = ss.str()).c_str();

But that function does not behave well.

Implicit conversion operators can easily be called by accident. This one
- has side effects which might occur by accident;
- has side effects which invalidate pointers that were returned by prior calls to the same function.
- is expensive.

Consider replacing it with a function like this:
1
2
3
4
5
6
std::string GetFullName() 
{
  ostringstream ss;
  ss << Name << " " << MiddleInitial << ". " << LastName << endl;
  return ss.str();
}


If you really want the conversion operator, consider modifying its signature like this:
explicit operator const char* () & { /*...*/ }
This is a little better but doesn't fix the underlying problems.
Last edited on
Aside from it being a dangling pointer like others have already mentioned, note that the bolded parentheses here are redundant and don't help/fix anything:
return ((ss.str()).c_str());
Last edited on
If you really need to return a char* pointer, then try this:

1
2
3
4
5
6
operator const char* ()
{
    ostringstream ss;
    ss << Name << " " << MiddleInitial << ". " << LastName << endl;
    return strdup(ss.str().c_str());
}

strdup() creates a new copy of the C string on the "heap", which survives after your function returns.

Note: Now the caller of your function "owns" the memory (i.e. the new copy of the string) and must free() it, eventually!

I'd vote for returning a std::string object, if that is possible for you, though.
Last edited on
uffaaa, there I go dangling once again and i was so positive the code should work
ss.str().c_str() and it does on its own.

what is the whole operator [const char*], this thing is not even on the list
https://en.cppreference.com/w/cpp/language/operators

i was just trying to send a print from my class like this
cout << myObject;

I can also add to cout stream like this and not have to call del
 
ostream& operator << (ostream& os, const Mazola& cMazola)


I am interested in this and never seen it
explicit operator const char* () & { /*...*/ }
You could use C++20's formatting library to do what you want for output, it is versatile like C's printf family, and is type-safe.

std::format returns its output to a std::string, you don't have to immediately cout it as many examples show.
This is where your thinking is confused.

C++ was designed around the << and >> operators being the magic to convert to and from strings!

We must look at it that way. You’ve basically already done it.

1
2
3
4
5
6
7
struct Mazola
{
  std::ostream & operator << ( std::ostream & os, const Mazola & m )
  {
    return os << m.Name << " " << m.MiddleInitial << ". " << m.LastName;
  }
};

That’s it! You can now turn any random Mazola into a string:

1
2
3
4
5
6
7
8
std::string mazola_s;
{
  std::ostringstream oss;
  oss << my_mazola;
  mazola_s = oss.str();
}

std::cout << mazola_s << "\n";

You can even have a useful function to make that prettier:

1
2
3
4
5
6
7
8
template <typename T>
std::string
to_string( const T & something )
{
  std::ostringstream oss;
  oss << something;
  return oss.str();
}
1
2
3
auto mazola_s = to_string( my_mazola );

std::cout << mazola_s << "\n";

Once you have a string, you can also pass it to functions requiring a const char * argument — so long as that function is done with it by the time it returns.

 
printf( "%s\n", to_string( my_mazola ).c_str() );

Hope this helps make better sense of it.
i already had it working with the const char* and << operator. today was a play day with them to see what I can modify and make work differently so i can understand them better.

you have all given me more to play with and thanks
1
2
3
4
5
		operator const char* ()
	{
		FullName = std::format("{} {}. {}",Name, MiddleInitial, LastName);
		return FullName.c_str();
	}
Thanks kigar64551, i put that in my bag of tricks
THanks Duthomhas
That’s still UB.

If you owned the character data then you could do that, just like std::string does.

But you don’t own it — your std::string does. It does not matter that the string is part of your class, or that it appears to work properly on your computer, with your compiler, version, etc. It’s a failure that hasn’t happened yet.

If you want to do something like that, you need to own (and manage) the resource yourself. Only then is it safe to let other actors take a peek, all while stipulating the same restrictions that .c_str() (and .data()) do.

A very easy way is to use a std::unique_ptr. Here’s an example of how to do it safely:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// This example shows how to automatically manage a raw memory resource
// that your class can let other actors look at, much like std::string::c_str().

#include <cstdio>
#include <memory>  // std::unique_ptr
#include <string>
#include <vector>

struct FullName
{
    std::vector <std::string> given_names;
    std::vector <std::string> surnames;

    FullName(
        const std::vector <std::string> & given_names,
        const std::vector <std::string> & surnames )
    : given_names {given_names}
    , surnames    {surnames}
    { }

    const char * c_str()
    {
        std::size_t n = 0, k = 0;
        for (const auto & s : given_names) k++, n += s.size();
        for (const auto & s : surnames)    k++, n += s.size();

        _full_name.reset( new char[ n + k ] );

        char * p = _full_name.get();
        n = 0;
        for (const auto & s : given_names) { n += s.copy( p+n, s.size() ); p[n++] = ' '; }
        for (const auto & s : surnames)    { n += s.copy( p+n, s.size() ); p[n++] = '-'; }
        p[n-1] = '\0';

        return p;
    }

private:
    std::unique_ptr <char> _full_name;
};


int main()
{
    FullName name { { "John", "Jacob" }, { "Jingleheimer", "Schmidt" } };

    // The hard way
    printf( "\"" );
    for (const auto & name : name.given_names) printf( "%s ", name.c_str() );
    for (const auto & name : name.surnames)    printf( "%s-", name.c_str() );
    printf( "\b\" -- printed each name component individually\n" );

    // The easy way
    printf( "\"%s\" -- printed all at once as a single .c_str()\n", name.c_str() );

    // Let's change the name!
    name.given_names.assign({ "Jenna", "Jean" });

    printf( "\"%s\"\n", name.c_str() );
}
"John Jacob Jingleheimer-Schmidt" -- printed each name component individually
"John Jacob Jingleheimer-Schmidt" -- printed all at once as a single .c_str()
"Jenna Jean Jingleheimer-Schmidt"

The string data is owned by the FullName class (conveniently managed for us by a std::unique_ptr) but is not guaranteed to continue to exist in any usable way outside of the same use-cases as std::string::c_str().

Hope this helps.

EDIT: Forgot to fix tabs to properly format the code.
Last edited on
Return const char*


With a modern C++ compiler the short answer is don't. Return std::string.

Historically a function return value was by-value (ie the value was copied with the usual copy overhead). However with modern copy elision (which omits copy and move constructors) the result is zero-copy pass-by-value semantics. See https://en.cppreference.com/w/cpp/language/copy_elision

Hence historically a char* was returned to avoid the copy overhead of returning a std::string. But that reason is no longer valid as returning a std::string now doesn't involve a copy.

Also note that strdup() is in c23. For MS VS until implemented use _strdup()
This was just a throw away program to practice my overloaded operators. i dont care if the object goes out of scope and the destructor is called here, it happens at the end of my program. I will never pass the const char* to another value anywhere other than cout temporarily and make an endeavor to use it otherwise and i will never create a pointer to the object then delete it and then try to cout the object after it has been deleted.

All i care about is practicing my overloaded operators by adding to the cout output age and date and changing those values from an o-op. i do care about age++ showing me immediately the result and not a statement later when the postfix already took place.

That being said and aside i do comprehend that you are showing me that this can be another point of entry issue in a larger file and when other programmers are involved and that it is advised to be done more fault tolerant. The advice is to pass as a string or own the returned pointer and manage it or let a smart pointer do the slavish work.

Duthomhas, that may be robust it uses up a lot of resources. Would you code it the same way for 10 million user names?

Streams can do conversions. Do you like to play ping-pong conversion with the built-in methods or with streams and why?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	
int x = 1;
string stringInt = "";

stringInt = to_string(x);
cout << stringInt << endl;

stringInt = "9";
x = stoi(stringInt);
cout << x << endl;


stringstream converterStream;
converterStream << x;
string inputAsStr;
converterStream >> inputAsStr;

cout << "Integer = " << x << endl;
cout << "String from integer = " << inputAsStr << endl;

stringstream anotherStream;
anotherStream << inputAsStr;
int Copy = 0;
anotherStream >> Copy;


Last edited on
My original response was not favorable. Alas, we are on the internet, and after re-reading your post I don’t think you are being deliberately rude. But your phrasing is rather aggressive.


Be aware, you may not care, but if you do it the way you are championing then you should not be surprised when things go sideways. The compiler can be very aggressive with optimizations, meaning that your raw data access may very well go out of scope before std::cout gets it. In the case of the strings it is unlikely to be an issue (assuming either lack of threads or proper synchronization).


Duthomhas, that may be robust it uses up a lot of resources. Would you code it the same way for 10 million user names?

Obviously not. I would not code it anything like that. I would design a better data structure than the one I was responding to.

But, were I absolutely required to design it poorly, and not require the user to call .c_str() on the individual components themselves, I’m sure I could manage some limited way to avoid eating up memory. A simple circular buffer of N strings may suffice.

The example was designed to highlight the difference between owned data and referenced data. But if that is not what people are getting out of it maybe I should strike it out.


Streams can do conversions. Do you like to play ping-pong conversion with the built-in methods or with streams and why?

Again, streams are designed for serialization. This is a pretty explicit concept in the streams library.

But whether the actual serialization/conversion/whatever you want to call it occurs using a stringstream or not is irrelevant. If it matters, profile the different methods apropos to your code and choose the one that works best. Otherwise use the most convenient.

Chances are, however, that the differences underneath, if any, are minor — for built-in data types at least. User code is a separate matter. Profile.

Given a choice, though, I won’t ping-pong anything. That’s a waste of time and resources.


The whole point of my original response was just to point out that you are working with a design flaw. While it worked for you (and as yet I am unsure you recognize why it is a problem), I absolutely disagree with trying to learn to build something using a broken design.

Do it all correctly, or you simply waste time doing one part as correctly as you can get it while the rest of it is just wrong. The consequence is that you have learned incorrectly on, IMHO, a significant level.


$0.02
Dear me, i was only explaining myself and my current simple needs and i apologize if taken alternatively. i respect all the answers and given the uniformity of the delivery I would be wise to heed the warnings and i especially appreciate your very detailed response, code, and time. The programmers here are masters of the craft and have a lot more experience than my confined code that only stays with me.

i did not dream up the const char* or << op code and i have seen it used by other programmers, so it is a surprise to me. Books and posts are all over that suggest not to pass by value where there is a choice but to do it using a ref or pointer to save on resources and time, and i obeyed. In fact, the structure of the overloaded forms are a copy (obviously) of what i have seen less the details of the cout output.

i have never experienced either of the two above not working but since it is being told to me by the collective experience i believe it. i would be more appreciative if i saw it firsthand even so. is there a known formula for a cout, maybe through some overexaggerated algorithm that will show the const char* expiration before the cout usage?

The problem is further elasticized if i have dozens and dozens of members that i may want to print at any time independently or as a unit and then millions of those. So at this very moment rather than having only a single point of possible flaw (return of the const char*), which is not an immediate issue for me, i have introduced myself many points of unknows in juggling your example based on case scenarios with my lack of experience.

So now what, a smart pointer for each of the values or a master smart pointer to a master string vector to all the values and then extrapolate them either way on the calling end? For you, this might be a relaxing venture, as you have tasted the bitterness and gargled the nuances of your solutions with your experiences.

Regardless, this ends up being a quick and immediate resolution versus a more long-term and less error prone one. I have used the op<< for now and i plan on returning to your sample code. I might write up code that shows all the ways mentioned above for printing from a class. i have to get other things out of the way first but i may post it here for further scrutiny.

QUESTION FOR ALL
Where have you first been exposed to this problem? Is this something that is in a C++ introductory book and in what section? Is this something in a more advanced book?

BTW, ping-pong was used in the specific sense, my specific ping-pong'ing the int to string and vice-versa, not in the general sense. Yes, i too have read that streams are used for this purpose, but it doesn't mean I see coders using it always and was curious on your positions on the matter. i understood the root of the problem being conveyed but the solution, based on different cases, remains clouded and unknown until i get into the trenches.
i have never experienced either of the two above not working


One of the issues with C++ is that you can write code that appears to work OK but is not correct code. Often the consequences of this incorrect code are not immediately apparent but some unrelated code may fail at some point - due to memory issues. This can be a nightmare to trace - especially in large/complex programs. I strongly suggest that you get in the habit of always writing 'correct' code - even for test or learning purposes. You will appreciate this later when you come to write these large programs.
Last edited on
If you are doing number/string conversions, have a look at std::from_chars() and std::to_chars():
https://en.cppreference.com/w/cpp/utility/from_chars
https://en.cppreference.com/w/cpp/utility/to_chars

Also std::format() can be used for number to string conversion. This gives you much more control over the format of the numeric string:
https://en.cppreference.com/w/cpp/utility/format/format
https://en.cppreference.com/w/cpp/utility/format/spec
Thank you seeplus. i have not seen from_chars/to_chars used yet and i will test it. I have seen std::format but there are times that format looks like an alien language, all depending on what exactly is being formatted. Something one has to get accustomed to.

I am still recovering from the shock and I have this cloud over me, what else might have i seen or learned that is a C++ issue waiting to happen.
there are times that format looks like an alien language


The format spec is based upon the Python way of formatting. Once you know that what's enclosed by {} is an argument from the argument list then you can work out what's meant by what's enclosed by the {}. If you have {} without anything enclosed then default formatting is used depending upon the argument type - which is often all you need. Eg:

1
2
3
const auto str {std::format("{} + {} + {}", 1, 2, 3)};

std::cout << str << '\n 


Displays:

1 + 2 + 3

For more info re the format spec see:
https://www.modernescpp.com/index.php/the-formatting-library-in-c20/
and following articles.

NB. Re std::to_chars()/std::from_chars(), also see:
https://www.cppstories.com/2019/11/tochars/
https://www.cppstories.com/2018/12/fromchars/
For my purpose here std::format is simple, what i implied was that it can look uglier and like an alien language in some more extreme cases and hence why i have put it on the back burner temporarily. i would like to become familiar with it as if i am reading a book eventually.

std::cout << std::format("Combined: {:*<+14.4f}, {:+#09x}\n", pi, 314);
std::cout << std::format("{:^05}|{:^05}|{:^5}|{:^5}|{:^5}\n", 1, -.2, "strn", 'c', false);

thanks

Have you all become familiar with std::format and have you warmed up to it, in all its case usage?
Last edited on
Pages: 12