Urgent: very strange results with my String class (operator +)

Hello. This is hard to explain and to make it easy to understand it came a bit long.

Look at this example:
1
2
3
4
GString myObj = "he";
myObj.Append("llo");

cout << myObj;     // operator<< overloaded for GString class 


The Append function just concatenates one GString with a char*

 
void Append(const char* toAdd);


This is the output of the program (debug couts included):

1
2
Constructor for (he) invoked
hello


The GString (implicit) constructor takes a char* to initialize myObj
Then we concatenate myObj with another char* ("llo") using the Append() function

All fine up until now... but look what happens with this code:

1
2
GString my = GString("h") + "llo";
cout << my;


The output? A mess:

1
2
3
4
5
6
7
8
Constructor for (h) invoked
Constructor for ( #nullptr#) invoked


Destructor for (h) invoked


 ( #nullptr# ) 


Alright, what the f**k is that "#nullptr#", you may ask.

Since I really mess up sometimes, I find really useful to use couts to Debug my code when running.

And... YES! That "#nullptr#" string belongs to my cout!


Look VERY CAREFULLY at the Constructor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GString::GString(const char* CstyleString)
{
        // USE_DEBUG is a macro that I set either to true or false to enable/disable printing
	if (USE_DEBUG)
	{
                // here, round brackets encase the CStyleString
		if (CstyleString) 
                     cout << "Constructor for (" << CstyleString << ")" << " invoked";

                // here, the "#nullptr#" string is printed out directly, without round brackets ( )
		else
                     cout << "Constructor for #nullptr#" << endl;
	}

        // builds the string with dynamic allocation ecc...
	Create(CstyleString);	
}


First point:
"llo" is a temporary and calls the Constructor.
In the Constructor, CstyleString's value should have been "llo"
Intead, I get that strange result

Second point:
In the second cout, I get this (look carefully):
Constructor for ( #nullptr#) invoked

This tells me the Constructor has been invoked, but CstyleString's value is not what I wanted.

NOW READ CAREFULLY:

Had CstyleString value been 'nullptr', then it would have printed just "#nullptr#" WITHOUT ROUND BRACKETS

Instead, I SEE A MIX!: I have "#nullptr# encased between two round brackets???

JUST... WHAT THE F!?!??!


************ WHAT IF I DISABLE THE "USE_DEBUG" MACRO? ************

Well... this is the output:

 
 #nullptr# 


*********************************************************
EDIT:
With the Visual Studio Debugger I noticed that when the Constructor for "llo" is called
CstyleString value is " #nullptr#"
My question is HOW THE HELL did that value go there?

EDIT 2:
If I completely comment the couts inside the Constructor I get

ed for (

It's a chopped string from the ~Destructor
*********************************************************

Guys, I know it's boring but please if you have any tips about what the heck is happening I'd be glad to hear!

For the complete implementation of the GString class, here is the link!
Really, if you have time, take a look at it
https://github.com/gedamial/GedamialLibrary/tree/master/gedamial/Data
Last edited on
You forgot to write the null character '\0' to the end of the string in operator+.
My operator+ is different from what you probably saw on GitHub.

I've updated it just some minutes before making the post:

1
2
3
4
5
6
7
8
GString GString::operator+(GString& other)
{
	cout << "In operator+ " << *this << endl;
	GString result = *this;
	result.Append(other);

	return result;
}


It now relies on the Append() function

FUN FACT:
the cout does not get printed inside the operator+() function!!!
Why isn't it being called?
What's happening?
Last edited on
My operator+ is different from what you probably saw on GitHub.

From the output with the code on github and your example in the OP, everything relevant is probably different on GitHub.

Push an update before you ask for help if you can't distill the code down to something you can post here in it's entirety that reproduces the problem.
Okay I've updated the GitHub repository
Last edited on
Any tip?

I really can't spot the problem!
> the cout does not get printed inside the operator+() function!!!
> Why isn't it being called?
> What's happening?

To see what's happening, add these lines to GString::operator char() const, rebuild and run the program.

1
2
3
4
5
6
7
8
9
GString::operator char() const
{
    // ****** added *************************
    std::cout << "GString::operator char() const  mainString == " << mainString
              << "  return mainString[0] == " << mainString[0] << '\n' << std::flush ;
    // ***************************************
              
    return mainString[0];
}
Last edited on
Thanks for the reply.

The entire output is the following:

1
2
3
4
5
6
***************
Creation process started for (h)
Creation process ended
***************

operator char() mainString == h >> return mainString[0] == h

1
2
3
4
5
6
7
8
9
***************
Creation process started for ()        // empty??? alright...
Creation process ended
***************

***************
Destructor process started for (h)
Destruction process ended
***************


As you can see, the operator char() gets called after the constructor of the temporary GString("h") ... i'm not sure why though...
Last edited on
GString my = GString( "h" ) + "llo"; is evaluated as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const GString temp_gstr( "h" ) ;
static const char literal[] = "llo" ;

///////////// temp_gstr + literal /////////////////////////////////
const char temp_char = temp_gstr.operator char() ;  // 'h'
const std::ptrdiff_t temp_offset = temp_char ; // std::ptrdiff_t(h)

const char* const temp_pointer = literal ;

// undefined behaviour unless 'h' >= 0  &&  'h' <= sizeof( "llo" ) ;
const char* const plus_result = temp_pointer + temp_offset ;

//////////////////////////////////////////////////////////////////////

GString my = plus_result ;


To get the expected behaviour:

a. remove the implicit conversion from const GString to char
1
2
// operator char() const; 
explicit operator char() const; 


b. make operator+() const correct
1
2
// GString operator+( GString& other ) ;
GString operator+( const GString& other ) const ;
(1) Why is that assignment evaluated in that way? And how did you know THAT was happening?

(2) Also, why isn't "llo" constructed with a GString object using the converting constructor?

(3) Could you explain me the evaluation of GString my = GString( "h" ) + "llo"; in words instead of code? Because by the code it looks weird. It's like... it's doing unexpected things

Thanks for your time!!!
> Why is that assignment evaluated in that way? And how did you know THAT was happening?
> why isn't "llo" constructed with a GString object using the converting constructor?

The value category of the expression GString( "llo" ) is prvalue ("pure" rvalue).
Therefore GString operator+( GString& other ) is not viable (it requires a non-const lvalue argument)

The only viable possibility for that construct involving the + operator is integer + pointer; and that is selected.

Details:
Call to an overloaded operator
If at least one of the arguments to an operator in an expression has a class type or an enumeration type, both builtin operators and user-defined operator overloads participate in overload resolution ...
...
Viable functions
Given the set of candidate functions, constructed as described above, the next step of overload resolution is examining arguments and parameters to reduce the set to the set of viable functions
...
5) If any parameter has reference type, reference binding is accounted for at this step: if an rvalue argument corresponds to non-const lvalue reference parameter or an lvalue argument corresponds to rvalue reference parameter, the function is not viable.
http://en.cppreference.com/w/cpp/language/overload_resolution
(1) You said that, since my GString::operator+ is not a valid candidate (thus, not viable), an integer+pointer operation is performed.
I suppose, from your code, the integer is ptrdiff_t and the pointer temp_pointer
But that still doesn't make sense to me. How is that code generated?

(2) Now I understand why I need to change my operator+ parameter, making it a const lvalue reference.
What I still don't understand is operator char() role here.
Why is it called (and so needs to be explicit?)

edit: thanks for your time!!

Last edited on
> How is that code generated?

1
2
3
4
5
6
7
8
static const char literal[] = "abcdefghijklmnopqrstuvwxyz" ;
const char GString = 20 ;

// implicit conversion from GString to integer
// implicit conversion from literal to pointer
const auto p = GString + literal ;

std::cout << p << '\n' ;



> Why is it called (and so needs to be explicit?)

If the operator+ is viable, and the operator char() allows an implicit conversion, there would be two equally good viable candidates resulting in an ambiguous overload.

1
2
3
evaluate: temp_gstring + literal
viable candidate: temp_gstring + gstring(literal) // user-defined conversion
viable candidate: int(temp_gstring) + literal // user-defined conversion 
I have tried this code (without the corrections you suggested)

1
2
auto a = GString("h") + "llo";
cout << typeid(a).name();


The output is const char*


Let's bring back your code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This is my "llo" treated as a c-style string literal, OK
static const char literal[] = "llo" ;

// Why are you declaring my "h" as a char?
// Maybe because of the implicit user-defined conversion from GString to char???
const char GString = 20;
// Alright but who makes this conversion and WHY?
// I mean... I can't find a reason for the overload resolutor to choose that conversion!

// YOU SAID: "implicit conversion from GString to integer"
// But it's not from GString to integer, it's from CHAR to INTEGER, since you declared the variable GString as CHAR
const auto p = GString + literal ;

std::cout << p << '\n' ;

The char type is an integer type in C++.
So basically my code behaves like:

1
2
3
4
5
6
7
char* pointer = new char[6] {"hello"};
int integer = 20;
char character = 20;

// Both couts print the same thing, since 'character' in converted to type int
cout << integer + pointer << endl;
cout << character + pointer;


Am I right?
Last edited on
No, character is not converted to an int. character is already an integer so no conversion is needed.
Last edited on
Topic archived. No new replies allowed.