I've been having really freaky stuff happening in my code. I believe I have tracked it down to the part labeled "here" (code is simplified, of course):
std::string func() {
char c;
// Do stuff that will assign to c
return "" + c; // Here
}
All sorts of stuff will happen when I try to cout the result of this function. I think I've even managed to get pieces of underlying C++ documentation, and many a segmentation fault. It's clear to me that this doesn't work in C++ (I've resorted to using stringstream to do conversions to string now), but I would like to know why. After using lots of C# for quite a while and no C++, this has caused me a lot of pain.
"" is a string literal. Those have the type array of N const char. This particular string literal is an array of 1 const char, the one element being the null terminator.
Arrays easily decay into pointers to their first element, e.g. in expressions where a pointer is required.
lhs + rhs is not defined for arrays as lhs and integers as rhs. But it is defined for pointers as the lhs and integers as the rhs, with the usual pointer arithmetic.
char is an integral data type in (i.e., treated as an integer by) the C++ core language.
==> string literal + character therefore is interpreted as pointer + integer.
The expression "" + c is roughly equivalent to:
static char const lit[1] = {'\0'};
char const* p = &lit[0];
p + c // "" + c is roughly equivalent to this expression
You return a std::string. The expression "" + c yields a pointer to const char. The constructor of std::string that expects a const char* expects it to be a pointer to a null-terminated character array.
If c != 0, then the expression "" + c leads to Undefined Behaviour:
For c > 1, the pointer arithmetic produces Undefined Behaviour. Pointer arithmetic is only defined on arrays, and if the result is an element of the same array.
If char is signed, then c < 0 produces Undefined Behaviour for the same reason.
For c == 1, the pointer arithmetic does not produce Undefined Behaviour. That's a special case; pointing to one element past the last element of an array is allowed (it is not allowed to use what it points to, though). It still leads to Undefined Behaviour since the std::string constructor called here requires its argument to be a pointer to a valid array (and a null-terminated string). The one-past-the-last element is not part of the array itself. Violating this requirement also leads to UB.
What probably now happens is that the constructor of std::string tries to determine the size of the null-terminated string you passed it, by searching the (first) character in the array that is equal to '\0':
string(char const* p)
{
// simplified
char const* end = p;
while(*end != '\0') ++end;
//...
}
this will either produce an access violation, or the string it creates contains "garbage".
It is also possible that the compiler assumes this Undefined Behaviour will never happen, and does some funny optimizations that will result in weird behaviour.
By the way, clang++3.5 emits a nice warning for this snippet:
warning: adding 'char' to a string does not append to the string
[-Wstring-plus-int]
return "" + c; // Here
~~~^~~
note: use array indexing to silence this warning
There are a lot of explanations of how the compiler interprets this code, but what you probably wanted to know is what you did wrong.
You appear to be expecting the + behavior from std::string. The problem is that neither of the operands actually is a std::string. C++ looks at the types of the operands, not the final type of the expression (here the return type, std::string) to resolve overloading. It won't pick std::string's version of + if it doesn't see a std::string.
If you have special behavior for an operator (either you wrote it, or got a library that provides it), that behavior only applies when at least one of the operands has class type (or reference to class type, and user-defined enumerations count too).
If you wrote
std::string("") + c
or
std::string() + c
or
""s + c // requires C++14
then you would get the std::string behavior of operator +.
(Note that none of these are actually good solutions, because they all make short-lived std::string instances that can be avoided with std::string(1, c))
The same thing goes for functions. Here's an example:
std::complex<double> ipi = std::log(-1.0);
You'll get a runtime error, instead of the expected imaginary number. That's because the compiler has no clue that it should be using the complex logarithm here. Overloading looks only at the arguments, and the argument is a real number (type double, actually).
Operator overloads ARE functions and obey the same rules.
This return statement
return "" + c;
is valid. There is used so called the pointer arithmetic. String literal "" is converted to pointer to its first character (in this case to its terminating zero) and integer value stored in c is added to the pointer.
So the result of expression
"" + c
has type const char *
Class std::string has conversion constructor that accepts argument of type const char *. The problem is that this pointer can points to beyond the string literal. So the function has undefined behaviour.
I do not see any sense in using this expression. If you want to build a string based on one character you could write for example
return std::string( 1, c );
the difference between C++ and C# is that in C# string literals have type System.String that has overloaded operator + for strings and characters (that are unicode characters in C#). In C++ string literals are constant character arrays and the semantic of operator + for arrays and integers are different. Arrays are converted to pointers to their first elements and there are used the pointer arithmetic.
It is standard class std::string that has overloaded operator + for characters. String literals in C++ are not objects of this class that is of type std::string.
Related
I've been having really freaky stuff happening in my code. I believe I have tracked it down to the part labeled "here" (code is simplified, of course):
std::string func() {
char c;
// Do stuff that will assign to c
return "" + c; // Here
}
All sorts of stuff will happen when I try to cout the result of this function. I think I've even managed to get pieces of underlying C++ documentation, and many a segmentation fault. It's clear to me that this doesn't work in C++ (I've resorted to using stringstream to do conversions to string now), but I would like to know why. After using lots of C# for quite a while and no C++, this has caused me a lot of pain.
"" is a string literal. Those have the type array of N const char. This particular string literal is an array of 1 const char, the one element being the null terminator.
Arrays easily decay into pointers to their first element, e.g. in expressions where a pointer is required.
lhs + rhs is not defined for arrays as lhs and integers as rhs. But it is defined for pointers as the lhs and integers as the rhs, with the usual pointer arithmetic.
char is an integral data type in (i.e., treated as an integer by) the C++ core language.
==> string literal + character therefore is interpreted as pointer + integer.
The expression "" + c is roughly equivalent to:
static char const lit[1] = {'\0'};
char const* p = &lit[0];
p + c // "" + c is roughly equivalent to this expression
You return a std::string. The expression "" + c yields a pointer to const char. The constructor of std::string that expects a const char* expects it to be a pointer to a null-terminated character array.
If c != 0, then the expression "" + c leads to Undefined Behaviour:
For c > 1, the pointer arithmetic produces Undefined Behaviour. Pointer arithmetic is only defined on arrays, and if the result is an element of the same array.
If char is signed, then c < 0 produces Undefined Behaviour for the same reason.
For c == 1, the pointer arithmetic does not produce Undefined Behaviour. That's a special case; pointing to one element past the last element of an array is allowed (it is not allowed to use what it points to, though). It still leads to Undefined Behaviour since the std::string constructor called here requires its argument to be a pointer to a valid array (and a null-terminated string). The one-past-the-last element is not part of the array itself. Violating this requirement also leads to UB.
What probably now happens is that the constructor of std::string tries to determine the size of the null-terminated string you passed it, by searching the (first) character in the array that is equal to '\0':
string(char const* p)
{
// simplified
char const* end = p;
while(*end != '\0') ++end;
//...
}
this will either produce an access violation, or the string it creates contains "garbage".
It is also possible that the compiler assumes this Undefined Behaviour will never happen, and does some funny optimizations that will result in weird behaviour.
By the way, clang++3.5 emits a nice warning for this snippet:
warning: adding 'char' to a string does not append to the string
[-Wstring-plus-int]
return "" + c; // Here
~~~^~~
note: use array indexing to silence this warning
There are a lot of explanations of how the compiler interprets this code, but what you probably wanted to know is what you did wrong.
You appear to be expecting the + behavior from std::string. The problem is that neither of the operands actually is a std::string. C++ looks at the types of the operands, not the final type of the expression (here the return type, std::string) to resolve overloading. It won't pick std::string's version of + if it doesn't see a std::string.
If you have special behavior for an operator (either you wrote it, or got a library that provides it), that behavior only applies when at least one of the operands has class type (or reference to class type, and user-defined enumerations count too).
If you wrote
std::string("") + c
or
std::string() + c
or
""s + c // requires C++14
then you would get the std::string behavior of operator +.
(Note that none of these are actually good solutions, because they all make short-lived std::string instances that can be avoided with std::string(1, c))
The same thing goes for functions. Here's an example:
std::complex<double> ipi = std::log(-1.0);
You'll get a runtime error, instead of the expected imaginary number. That's because the compiler has no clue that it should be using the complex logarithm here. Overloading looks only at the arguments, and the argument is a real number (type double, actually).
Operator overloads ARE functions and obey the same rules.
This return statement
return "" + c;
is valid. There is used so called the pointer arithmetic. String literal "" is converted to pointer to its first character (in this case to its terminating zero) and integer value stored in c is added to the pointer.
So the result of expression
"" + c
has type const char *
Class std::string has conversion constructor that accepts argument of type const char *. The problem is that this pointer can points to beyond the string literal. So the function has undefined behaviour.
I do not see any sense in using this expression. If you want to build a string based on one character you could write for example
return std::string( 1, c );
the difference between C++ and C# is that in C# string literals have type System.String that has overloaded operator + for strings and characters (that are unicode characters in C#). In C++ string literals are constant character arrays and the semantic of operator + for arrays and integers are different. Arrays are converted to pointers to their first elements and there are used the pointer arithmetic.
It is standard class std::string that has overloaded operator + for characters. String literals in C++ are not objects of this class that is of type std::string.
Here is the code I'm having trouble to understand:
char* myPtr = "example";
myPtr[1] = 'x';
How am I allowed to use myPtr[1]? Why can I choose positions like a do on arrays? myPtr is not even an array.
Obs. I know about lookup table, literal pooling and string literals, my concern is just how this even compile. I don't use pointers that much.
Can anyone help?
Apparently you made an assumption that applicability of [] operator to something necessarily implies that that "something" is an array. This is not true. The built-in [] operator has no direct relation to arrays. The [] is just a shorthand for a combination of * and + operators: by definition a[b] means *(a + b), where one operand is required to be a pointer and another is required to be an integer.
Moreover, when you apply the [] operator to an actual array, that array gets implicitly converted to a pointer type first, and only then the resultant pointer can act as an operand of [] operator. This actually means the opposite of what you supposedly assumed initially: operator [] never works with arrays. By the time we get to the [] the array has already decayed to a pointer.
As a related side-note, this latter detail manifests itself in one obscure peculiarity of the first C language standard. In C89/90 the array-to-pointer conversion was not allowed for rvalue arrays, which also prevented the [] operator from working with such arrays
struct S { int a[10]; };
struct S foo(void) { struct S s = { 0 }; return s; }
int main()
{
foo().a[5];
/* ERROR: cannot convert array to pointer, and therefore cannot use [] */
return 0;
}
C99 expanded the applicability of that conversion thus making the above code valid.
It compiles according to §5.2.1/1 [expr.sub] of the C++ standard:
A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall have the type “array of T” or “pointer to T” and the other shall have unscoped enumeration or integral type. The result is of type “T”. The type “T” shall be a completely-defined object type.
The expression E1[E2] is identical (by definition) to *((E1)+(E2)), except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise.
Since "example" has type char const[8] it may decay to char const* (it used to decay to char* as well, but it's mostly a relict of the past) which makes it a pointer.
At which point the expression myPtr[1] becomes *(myPtr + 1) which is well defined.
Pointers hold the address of memory location of variables of specific data types they are assigned to hold. As others have pointed out its counter-intuitive approach take a bit of learning curve to understand.
Note that the string "example" itself is immutable however, the compiler doesn't prevent the manipulation of the pointer variable, whose new value is changed to address of string 'x' (this is not same as the address of x in 'example'),
char* myPtr = "example";
myPtr[1] = 'x';
Since myPtr is referencing immutable data when the program runs it will crash, though it compiles without issues.
From C perspective, here, you are dereferencing a mutable variable.
By default in C, the char pointer is defined as mutable, unless specifically stated as immutable through keyword const, in which case the binding becomes inseparable and hence you cannot assign any other memory address to the pointer variable after defining it.
Lets say your code looked like this,
const char *ptr ="example";
ptr[1] = 'x';
Now the compilation will fail and you cannot modify the value as this pointer variable is immutable.
You should use char pointer only to access the individual character in a string of characters.
If you want to do string manipulations then I suggest you declare an int to store each character's ASCII values from the standard input output like mentioned here,
#include<stdio.h>
int main()
{
int countBlank=0,countTab=0,countNewLine=0,c;
while((c=getchar())!=EOF)
{
if(c==' ')
++countBlank;
else if(c=='\t')
++countTab;
else if(c=='\n')
++countNewLine;
putchar(c);
}
printf("Blanks = %d\nTabs = %d\nNew Lines = %d",countBlank,countTab,countNewLine);
}
See how the integer takes ASCII values in order to get and print individual characters using getchar() and putchar().
A special thanks to Keith Thompson here learnt some useful things today.
The most important thing to remember is this:
Arrays are not pointers.
But there are several language rules in both C and C++ that can make it seem as if they're the same thing. There are contexts in which an expression of array type or an expression of pointer type is legal. In those contexts, the expression of array type is implicitly converted to yield a pointer to the array's initial element.
char an_array[] = "hello";
const char *a_pointer = "goodbye";
an_array is an array object, of type char[6]. The string literal "hello" is used to initialize it.
a_pointer is a pointer object, of type const char*. You need the const because the string literal used to initialize it is read-only.
When an expression of array type (usually the name of an array object) appears in an expression, it is usually implicitly converted to a pointer to its initial (0th) element. So, for example, we can write:
char *ptr = an_array;
an_array is an array expression; it's implicitly converted to a char* pointer. The above is exactly equivalent to:
char *ptr = &(an_array[0]); // parentheses just for emphasis
There are 3 contexts in which an array expression is not converted to a pointer value:
When it's the operand of the sizeof operator. sizeof an_array yields the size of the array, not the size of a pointer.
When it's the operand of the unary & operator. &an_array yields the address of the entire array object, not the address of some (nonexistent) char* pointer object. It's of type "pointer to array of 6 chars", or char (*)[6].
When it's a string literal used as an initializer for an array object. In the example above:
char an_array[] = "hello";
the contents of the string literal "hello" are copied into an_array; it doesn't decay to a pointer.
Finally, there's one more language rule that can make it seem as if arrays were "really" pointer: a parameter defined with an array type is adjusted so that it's really of pointer type. You can define a function like:
void func(char param[10]);
and it really means:
void func(char *param);
The 10 is silently ignored.
The [] indexing operator requires two operands, a pointer and an integer. The pointer must point to an element of an array object. (A standalone object is treated as a 1-element array.) The expression
arr[i]
is by definition equivalent to
*(arr + i)
Adding an integer to a pointer value yields a new pointer that's advanced i elements forward in the array.
Section 6 of the comp.lang.c FAQ has an excellent explanation of all this stuff. (It applies to C++ as well as to C; the two languages have very similar rules in this area.)
In C++, your code generates a warning during compile:
{
//char* myPtr = "example"; // ISO C++ forbids converting a string
// constant to ‘char*’ [-Wpedantic]
// instead you should use the following form
char myPtr[] = "example"; // a c-style null terminated string
// the myPtr symbol is also treated as a char*, and not a const char*
myPtr[1] = 'k'; // still works,
std::cout << myPtr << std::endl; // output is 'ekample'
}
On the other hand, std::string is much more flexible, and has many more features:
{
std::string myPtr = "example";
myPtr[1] = 'k'; // works the same
// then, to print the corresponding null terminated c-style string
std::cout << myPtr.c_str() << std::endl;
// ".c_str()" is useful to create input to system calls requiring
// null terminated c-style strings
}
The semantics of abc[x] is "Add x*sizeof(type)" to abc where abc is any memory pointer. Arrays variable behave like memory pointers and they just point to beginning of the memory location allocated to array.
Hence adding x to array or pointer variable both will point to memory which is same as variable pointing to + x*sizeof(type which array contains or pointer points to, e.g. in case of int pointers or int array it's 4)
Array variables are not same as pointer as said in comment by Keith as array declaration will create fix sized memory block and any arithmetic on that will use size of array not the element types in that array.
I was under the impression that comparison operators are not defined for C-style strings, which is why we use things like strcmp(). Therefore the following code would be illegal in C and C++:
if("foo" == "foo"){
printf("The C-style comparison worked.\n");
}
if("foo" == "bob"){
printf("The C-style comparison produced the incorrect answer.\n");
} else {
printf("The C-style comparison worked, strings were not equal.\n");
}
But I tested it in both Codeblocks using GCC and in VS 2015, compiling as C and also as C++. Both allowed the code and produced the correct output.
Is it legal to compare C-style strings? Or is it a non-standard compiler extension that allows this code to work?
If this is legal, then why do people use strcmp() in C?
The compiler is free to use string interning, i.e. save memory by avoiding to duplicate identical data. The 2 "foo" literals that compare equal must be stored in the same memory location in your case.
However, you should not take this as the rule. The strcmp method will work under all circumstances, whereas it is implementation defined whether your observation will hold with another compiler, compiler version, compilation flags set etc.
The code is legal in C. It just may not produce the result you expected.
The type of string literal is char[N] in C and const char[N] in C++, where N is the number of characters in the string literal.
"foo" is type char[4] and const char[4] in C and C++ respectively. Basically it's an array. An array gets converted into a pointer to its first element when used in an expression. So in the comparison, if("foo" == "foo") the string literals get converted into pointers. Hence, the "address comparison".
In the comparison,
if("foo" == "foo"){
the addresses of the string literals are compared, which may or may not be equal.
It is equivalent to:
const char *p = "foo";
const char *q = "foo";
if ( p == q) {
...
}
C standard doesn't guarantee that addresses are equal for two string literals with same content ("foo"'s here) are placed in same location. But in practice, any compiler would place at the same address. So the comparison seems to work. But you can't rely on this behaviour.
6.4.5, String literals (C11, draft)
It is unspecified whether these arrays are distinct provided their
elements have the appropriate values. If the program attempts to
modify such an array, the behavior is undefined.
Similarly, this comparison
if("foo" == "bob"){
...
}
is equivalent to:
const char *x = "foo";
const char *y = "bob";
if("foo" == "bob"){
...
}
In this case, the string literals would be at different locations and pointer comparison fails. So in both cases, it looks as if the == operator actually works for comparing C-strings.
Instead if you do comparisons using arrays, it will not work:
char s1[] ="foo";
char s2[] = "foo";
if (s1 == s2) {
/* always false */
}
The difference is that when an array is initialized with a string literals, it's copied into the array. The arrays s1 and s2 have distinct the addresses and will never be equal. But in case of string literals, both p and q point to the same address (assuming the compiler places so - this is not guaranteed as noted above).
it is copying/comparing the addresses of the the string, not the content of the strings.
comparing the addresses is a valid operation
I'm studying about the C++ library type string. I was looking into operation that the type can perform, among which are concatenation.
I know that in C++ operators can be overloaded to suit class types needs,and I know that character string literals are const char arrays under the hood.
So, I know that the string type defines a constructor that takes a string literal argument and converts it into a string, so that I can initialize a string using:
string s="hello!"
what happens here is an impicit conversion (through the constructor that takes a const char*) to string and then a copy initialization is performed (through the copy-constructor of string).
so far so good.. (correct me if I am getting something wrong so far)
Now, I know that I can concatenate two strings, or I can concatenate a string and a char literal (or vice versa) and a string and a string literal (so I suppose the first happens because there's an overloaded + operator that takes symmetrically a char and a string, the latter happens thanks to the overloaded + opeartor that concatenates two strings and the implicit conversion from const char* to string). My questions are:
1: why can't I concatenate two string literals? shouldn't the operator+ that takes two strings be called (and two implicit conversion performed from const char* to string) ?
string s="hi"+" there";
2: I tried to concatenate a char literal and a string literal (calling the operator+ that takes a char and a string) but I get only garbage in the resulting string, as the compiler doesn't mind, but the result is certainly not what I want.
string s='h'+"ello!";
cout<<s<<endl;
I get garbage out.If a operator+(char,const string&) exists it should be called after implicitly converting "ello" to string shouldn't it?
can you please explain?
1) Concatenating two string literals.
"Hello" + " world"
The compiler has no hint that it should be looking for anything related to std::string -- it is looking at two const char[] objects, and you cannot + those (or the const char * they could be degraded to).
2) Using operator+ on a char literal and a string literal.
(Thanks to User dyp for pointing in the right direction.)
If you literally mean a char literal -- e.g. 'a' -- then you're running afoul of one of the more surprising things C/C++ have to offer.
Consider: a[i] is equivalent to i[a]. (This is a fact.)
So, if you write:
'a' + "Hello"
...that is equivalent to (in ASCII)...
"Hello" + 97
...which is a pointer into nowhere, i.e. constructing a std::string from it is undefined behaviour.
Borgleader already gave half the answer in comment:
when adding two string litterals, compiler only sees two const char * and they could be cast to string but also to integers. So unless you explicitely say that you want them as strings, you get an error
when adding a string litteral and a char, compiler sees a pointer and an integral number. It knows how to process that and does pointer arithmetics on your C string - which is probably not what you expected! There are chances that you end past the end of the char array and invoke undefined behaviour. Examples:
"abc" + '0' => "abc" + 48 : 44 positions past the NULL ...
"abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" + '0' => "ij"
I've been having really freaky stuff happening in my code. I believe I have tracked it down to the part labeled "here" (code is simplified, of course):
std::string func() {
char c;
// Do stuff that will assign to c
return "" + c; // Here
}
All sorts of stuff will happen when I try to cout the result of this function. I think I've even managed to get pieces of underlying C++ documentation, and many a segmentation fault. It's clear to me that this doesn't work in C++ (I've resorted to using stringstream to do conversions to string now), but I would like to know why. After using lots of C# for quite a while and no C++, this has caused me a lot of pain.
"" is a string literal. Those have the type array of N const char. This particular string literal is an array of 1 const char, the one element being the null terminator.
Arrays easily decay into pointers to their first element, e.g. in expressions where a pointer is required.
lhs + rhs is not defined for arrays as lhs and integers as rhs. But it is defined for pointers as the lhs and integers as the rhs, with the usual pointer arithmetic.
char is an integral data type in (i.e., treated as an integer by) the C++ core language.
==> string literal + character therefore is interpreted as pointer + integer.
The expression "" + c is roughly equivalent to:
static char const lit[1] = {'\0'};
char const* p = &lit[0];
p + c // "" + c is roughly equivalent to this expression
You return a std::string. The expression "" + c yields a pointer to const char. The constructor of std::string that expects a const char* expects it to be a pointer to a null-terminated character array.
If c != 0, then the expression "" + c leads to Undefined Behaviour:
For c > 1, the pointer arithmetic produces Undefined Behaviour. Pointer arithmetic is only defined on arrays, and if the result is an element of the same array.
If char is signed, then c < 0 produces Undefined Behaviour for the same reason.
For c == 1, the pointer arithmetic does not produce Undefined Behaviour. That's a special case; pointing to one element past the last element of an array is allowed (it is not allowed to use what it points to, though). It still leads to Undefined Behaviour since the std::string constructor called here requires its argument to be a pointer to a valid array (and a null-terminated string). The one-past-the-last element is not part of the array itself. Violating this requirement also leads to UB.
What probably now happens is that the constructor of std::string tries to determine the size of the null-terminated string you passed it, by searching the (first) character in the array that is equal to '\0':
string(char const* p)
{
// simplified
char const* end = p;
while(*end != '\0') ++end;
//...
}
this will either produce an access violation, or the string it creates contains "garbage".
It is also possible that the compiler assumes this Undefined Behaviour will never happen, and does some funny optimizations that will result in weird behaviour.
By the way, clang++3.5 emits a nice warning for this snippet:
warning: adding 'char' to a string does not append to the string
[-Wstring-plus-int]
return "" + c; // Here
~~~^~~
note: use array indexing to silence this warning
There are a lot of explanations of how the compiler interprets this code, but what you probably wanted to know is what you did wrong.
You appear to be expecting the + behavior from std::string. The problem is that neither of the operands actually is a std::string. C++ looks at the types of the operands, not the final type of the expression (here the return type, std::string) to resolve overloading. It won't pick std::string's version of + if it doesn't see a std::string.
If you have special behavior for an operator (either you wrote it, or got a library that provides it), that behavior only applies when at least one of the operands has class type (or reference to class type, and user-defined enumerations count too).
If you wrote
std::string("") + c
or
std::string() + c
or
""s + c // requires C++14
then you would get the std::string behavior of operator +.
(Note that none of these are actually good solutions, because they all make short-lived std::string instances that can be avoided with std::string(1, c))
The same thing goes for functions. Here's an example:
std::complex<double> ipi = std::log(-1.0);
You'll get a runtime error, instead of the expected imaginary number. That's because the compiler has no clue that it should be using the complex logarithm here. Overloading looks only at the arguments, and the argument is a real number (type double, actually).
Operator overloads ARE functions and obey the same rules.
This return statement
return "" + c;
is valid. There is used so called the pointer arithmetic. String literal "" is converted to pointer to its first character (in this case to its terminating zero) and integer value stored in c is added to the pointer.
So the result of expression
"" + c
has type const char *
Class std::string has conversion constructor that accepts argument of type const char *. The problem is that this pointer can points to beyond the string literal. So the function has undefined behaviour.
I do not see any sense in using this expression. If you want to build a string based on one character you could write for example
return std::string( 1, c );
the difference between C++ and C# is that in C# string literals have type System.String that has overloaded operator + for strings and characters (that are unicode characters in C#). In C++ string literals are constant character arrays and the semantic of operator + for arrays and integers are different. Arrays are converted to pointers to their first elements and there are used the pointer arithmetic.
It is standard class std::string that has overloaded operator + for characters. String literals in C++ are not objects of this class that is of type std::string.