1

Casting this Vector3 into a const char* has worked in implicit and explicit conversions, but hasn't when attempting to convert it at construction time. Only then does const char* 'v3pchar' return blank. Any thoughts? The full code is below. Thanks!

#include <stdio.h>
#include <iostream>
#include <string>

using namespace std;

struct Vector3 {
    int x, y, z = 0;

    Vector3() {
        cout << "Vector3()" << endl;
    };
    
    Vector3(int X, int Y, int Z) : x(X), y(Y), z(Z) {
        cout << "Vector3(int X, int Y, int Z)" << endl;
    };

    // Focal point here
    operator const char* () {
        cout << "operator const char* called" << endl;
        v3string = to_string(x) + ", " + to_string(y) + ", " + to_string(z);
        v3pchar = v3string.c_str();
        return v3pchar;
    }

private:
    string v3string = "Never even blank";
    const char* v3pchar = "So why does this become blank?";
};

int main() {
    Vector3 v1 = Vector3(1, 2, 3);
    cout << "Vector3 implicit cast to char*: [" << v1 << "]" << endl; // Works

    const char* v2 = v1;
    printf("Vector3 explicit cast to char*: [%s]\n", (const char*)v2); // Works

    cout << "---------------------------------------------" << endl;

    const char* v3 = Vector3(4, 5, 6);
    cout << "Vector3 instantiation cast to char*: [" << v3 << "]" << endl; // Empty

    return 0;
};
joseph taylor
  • 123
  • 12
  • 3
    _`int x, y, z = 0;`_ You sure you didn't mean `int x = 0, y = 0, z = 0;`? – mediocrevegetable1 Jun 05 '21 at 17:59
  • @Richard Critten Oh right yeah! The pointer is pointing to a variable in a Vector3 that I didn't assign a space for in memory! I didn't consider it would delete it before it printed its value though. This is very insightful! Thank you! – joseph taylor Jun 05 '21 at 18:05
  • @mediocrevegetable1 The code still ran, is there a difference? – joseph taylor Jun 05 '21 at 18:10
  • 2
    Yes. What you have done only sets `z` to 0 and leaves `x` and `y` uninitialized, so if you call the constructor with no parameters, `x` and `y` will remain uninitialized. Using uninitialized variables is undefined behavior. `int x = 0, y = 0, z = 0;` sets all values to 0 to prevent this. – mediocrevegetable1 Jun 05 '21 at 18:19
  • 1
    @josephtaylor the values of `x` and `y` are not initialized by default. If your first constructor (taking no arguments) is used, they are never assigned a value. It is thus possible to end up with a `Vector3` object that is in a 'bad' state. Using that object - that is, doing anything with its `x` and `y` data members - would be undefined behaviour. You compiler can inform you of these things, if you compile with e.g. the `-Wall` flag. – Nelewout Jun 05 '21 at 18:19
  • It would be more "C++ - like" to define an `operator<<` method to output the content of the class in a string form. See https://learn.microsoft.com/en-us/cpp/standard-library/overloading-the-output-operator-for-your-own-classes I have never seen `operator const char*` used anywhere. – Den-Jason Jun 05 '21 at 18:21
  • 2
    Oh I didn't even realize! I thought it set them all to 0. Also didn't know << operator was a thing, and I will definitely be using that now. Thanks! – joseph taylor Jun 05 '21 at 18:33
  • 1
    Yes it's worth remembering that in most cases, returning a naked pointer from a function or class-method is asking for trouble; you have to be absolutely certain about the lifetime of whatever is being addressed. Use of smart pointers removes some of the risk. See https://www.learncpp.com/cpp-tutorial/returning-values-by-value-reference-and-address/ and https://stackoverflow.com/questions/752658/is-the-practice-of-returning-a-c-reference-variable-evil – Den-Jason Jun 05 '21 at 18:55

1 Answers1

3
   v3pchar = v3string.c_str();

The const char * pointer returned by c_str() is owned by its std::string, and is only valid until the std::string is modified, or it gets destroyed, whichever comes first. Capsule summary: as soon as v3string is modified or destroyed, v3pchar is no longer valid.

   const char* v3 = Vector3(4, 5, 6);

This results in the following sequence of events:

  1. A temporary Vector3 object gets created and constructed.
  2. Its operator const char* method gets called, and it returns a const char * owned by an internal std::string that's a member of the Vector3 object.
  3. The returned const char * is assigned to v3.
  4. The temporary Vector3 object gets destroyed, together with its std::string member.
  5. It follows the the originally-returned const char * pointer is no longer valid, since the std::string object that owns it is now destroyed.

Any subsequent use of this const char * pointer results in undefined behavior.

It should be possible to modify the shown code, using some features in modern C++ that would result in this construct becoming ill-formed (class method ref qualifiers), and having your C++ compiler catch this common bug. But that would be another question; as far as the reason for your observed results: it's undefined behavior for the reasons outlined above.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148