VS2010 shows error C2440: 'return' : cannot convert from 'char *' to 'const char *& in f3 below. Why is it not possible to return a char* when the return type is const char*&?
const char* const& f1()
{
static char* x = new char[5]();
return x;
}
char* const& f2()
{
static char* x = new char[5]();
return x;
}
const char* & f3()
{
static char* x = new char[5]();
return x; // error C2440
}
char* & f4()
{
static char* x = new char[5]();
return x;
}
From Paragraph 8.5.3/2 of the C++11 Standard:
A reference cannot be changed to refer to another object after initialization. Note that initialization of a
reference is treated very differently from assignment to it. Argument passing (5.2.2) and function value
return (6.6.3) are initializations.
This basically tells you that returning a value from a function is equivalent to performing an initialization. Therefore, function f3() doesn't compile for the same reason the last initialization in the code snippet below doesn't compile:
char c = 'a';
char* x = &c;
const char*& y = x; // ERROR!
The type of the object referenced by y is const char*, while the type of the expression we are trying to initialize it with (i.e. the type of x) is char*. Those are different types, and when binding a reference the types of the initializer expression and of the object referenced by the initialized reference must be identical (with an exception for base and derived classes, which are not involved here), apart from top-level cv qualifications.
Here, the const qualification in const char* is not a top-level qualification, because it applies to the pointed object, and not to the pointer itself: while the pointed char value cannot be modified through a const char* pointer, the pointer itself can be reassigned.
In Standard terms, this means that the types const char* and char* are not reference-related:
Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. [...]
On the other hand, function f2() compiles, consistently with the fact that the initialization in the last line below is legal:
char c = 'a';
char* x = &c;
char* const& y = x; // OK
Here, the type of the referenced object is char* const, which (unlike const char*) is reference-compatible with char* in the sense defined by the Paragraph quoted above *(the const qualification is a top-level qualification in this case).
In Particular, per 8.5.3/5:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
— If the reference is an lvalue reference and the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
[...]
The omitted part is not relevant to this case. Here, char* const and char* are reference-compatible, and the initialization is legal (and f2() compiles). On the other hand, const char* and char* are not reference-compatible, and the initialization is illegal (and f3() does not compile).
For the same reason you can't cast from char ** to const char **. Otherwise you could write
f3() = "hello";
and a subsequent call to f3 would be able to write to the memory of the string literal, aliased to the static local x.
#include <iostream>
void problem( const char*& output, const char* input )
{
output = input; // legal, const char*& = const char*
}
int main() {
const char buff[] = "hello";
char* out = nullptr;
problem( const_cast<const char*&>(out), &buff[0] );
out[0] = 'H'; // I just changed a const array's first character
std::cout << &buff[0] << "\n";
}
This prints "Hello", which is evidence I just engaged in undefined behavior. To do so, I had to use a const_cast. If you could initialize a const char*& with a char*, I wouldn't have to do the const_cast to invoke the undefined behavior.
"But" one might say "this doesn't match the exact case that the OP posted!"
Which is true.
// the "legal" way to do what the OP wants to do, but it leads to undefined
// behavior:
const char*& problem2( char*& c ) { return const_cast<const char*&>(c); }
void problem3( const char*& left, const char* right ) { left = right; }
#include <iostream>
int main() {
char* c = nullptr;
char const* buff = "hello undefined behavior";
problem3( problem2(c), buff );
c[0] = 'H';
std::cout << buff << "\n";
}
also prints "Hello undefined behavior".
"But", one might say, "you turned a char*& into a const char*&, not a char* into a const char*&.
Very well:
void problem3( const char*& left, const char* right ) { left = right; }
const char*& problem4( char x = 0 ) {
static char* bob = nullptr;
if (bob && x)
bob[0] = x;
return const_cast<const char*&>(bob);
}
#include <iostream>
int main() {
char const* buff = "hello problem";
problem3( problem4(), buff );
problem4('H');
std::cout << buff << "\n";
}
Again, that incriminating capital H (well, actually, undefined behavior that typically is a capital H).
Related
I have the following functions:
void func(void * const &ptr)
{
std::cerr << "const" << std::endl;
}
void func(void * &&ptr)
{
std::cerr << "mutable" << std::endl;
}
void* const func2()
{
return nullptr;
}
One overload takes const reference parameter and another takes mutable rvalue reference. And there is a function that returns const value.
When I pass that const temporary value to the function:
func(func2());
I expect the const overload to be chosen. But instead I get:
mutable
How is that possible? Why is const return value bound to non-const rvalue reference parameter?
This doesn't however happen when instead of void* I pass const struct to the function:
struct A
{
};
void func(A const &a)
{
std::cerr << "const" << std::endl;
}
void func(A &&a)
{
std::cerr << "mutable" << std::endl;
}
A const func3()
{
return A();
}
int main()
{
func(func3());
return 0;
}
The result is:
const
You can check this on coliru.
What is difference between const void* and const struct?
Is there a way to make overload that takes specifically const values?
Why is const temporary bound to rvalue reference parameter?
Because it's not const by the time overload resolution happens.
[expr.type]
2 If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
A class type prvalue retains its cv-qualifications, but not a void* const prvalue. Overload resolution therefore happens with a plain void* prvalue, which explains the behavior you observe when the rvalue overload is chosen.
The types this paragraph applies to are those "fundamental" types whose value is actually accessed by the program. So a prvalue of such a type is indeed a "pure", ephemeral value, and cannot be modified already.
int main()
{
const int a = 1;
const int b = 2;
typedef decltype(a*b) multiply_type;
cout << typeid(multiply_type).name() << endl;
return 0;
}
The return value of the program is that multiply_type is int. I'm quite surprised. I expected the type deduction to yield const int and since the expression yields a pr value, the resultant type would be const int.
PS: With auto the return value would be int as it drops the const qualifier.
Any ideas why multiply_type is int instead of const int with decltype ?
Edit: Added an addition example which is also related to cv-qualifier.
#include<iostream>
#include<typeinfo>
using namespace std;
struct Details
{
int m_age;
};
int main()
{
const Details* detail = new Details();
typedef decltype((detail->m_age)) age_type;
cout << typeid(age_type).name() << endl;
int a = 1;
age_type age = a;
age = 10; // This is not possible. Read only.
cout << typeid(age).name() << endl; // This returns the type as int though. Then why is 20 not possble ?
return 0;
}
Edit 2: Check our the link.
http://thbecker.net/articles/auto_and_decltype/section_07.html
`
int x;
const int& crx = x;
/ The type of (cx) is const int. Since (cx) is an lvalue,
// decltype adds a reference to that: cx_with_parens_type
// is const int&.
typedef decltype((cx)) cx_with_parens_type;`
decltype evaluate it argument as it is, decltype(i) where i is cv-qualified lvalue, results in declaring type cv-qualified, but the expression of i*i in decltype(i*i) create a non materialized prvalue with type of i with non cv-qualified, prvalue don't have an explicit notion of constness. your code produce the same as:
using T = const int;
static_assert(is_same<int, decltype(0)>(), "Failed");
The fact that typeid is not showing the cv-qualification is because they a ignored:
5.2.8.5 - If the type of the expression or type-id is a cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified type.
Consider the following code snippets:
void foo(const int i) // First foo
{
std::cout << "First " << i << endl;
}
void foo(int i) // Second foo
{
std::cout << "Second " << i << endl;
}
int main()
{
int i = 5;
foo(i);
}
Compilation Error:
redefinition of 'void foo(int)'
Since consts can be initialized with non-const objects, the above behaviour seems reasonable. Now consider this:
void foo_ptr(const int* p) // First foo_ptr
{
std::cout << "First " << *p << endl;
}
void foo_ptr(int* p) // Second foo_ptr
{
std::cout << "Second " << *p << endl;
}
int main()
{
int i = 5;
foo_ptr(&i); // Second foo_ptr gets called; prints 'Second 5'
}
As it might be clear, my question is - If the two definitions of foo in the first case are considered the same then why it is not so for foo_ptr in the second case? Or in other words, why const is ignored in the first case and not so in the second one?
const int* p
is not a constant pointer to an integer, it's a pointer to a constant integer (i.e., [const int] * p rather than const [int * p]). This is why you sometimes see code like:
const int * const p;
which may seem redundant to the uninitiated but is really not - p in that case is a pointer you're not allowed to change, which points to an integer you're also not allowed to change.
Hence the two functions you have in your second case are considered different in terms of the parameters accepted. That's also why you're calling the second function, since i is most definitely not a const integer.
In other words, while const-ing a parameter does not change it in terms of the function signature, that's not what you're doing here. Changing a parameter from "pointer to int" to "pointer to const int" does affect the signature.
The equivalent case to your first code snippet would be providing both of:
void foo_ptr (int * const p)
void foo_ptr (int * p)
During overload resolution, const and volatile specifies on parameters are significant except when they occur at the outermost level of the of the parameter type specification. From the C++ standard, § 13.1.3.4:
Parameter declarations that differ only in the presence or absence of
const and/or volatile are equivalent. That is, the const and
volatile type-specifiers for each parameter type are ignored when
determining which function is being declared, defined, or called. [
Example:
typedef const int cInt;
int f (int);
int f (const int); // redeclaration of f(int)
int f (int) { /* ... */ } // definition of f(int)
int f (cInt) { /* ... */ } // error: redefinition of f(int)
—end example ] Only the const and volatile type-specifiers at the
outermost level of the parameter type specification are ignored in
this fashion; const and volatile type-specifiers buried within a
parameter type specification are significant and can be used to
distinguish overloaded function declarations. In particular, for any
type T, “pointer to T,” “pointer to const T,” and “pointer to
volatile T” are considered distinct parameter types, as are “reference
to T,” “reference to const T,” and “reference to volatile T.”
Because when you declare
void foo(const int n)
{
}
All that the const modifier does is prevent n from being modified inside the foo() function. The parameter to this foo() function is still an int. The const modifier does not modify the parameter's type. So both
void foo(int n)
and
void foo(const int n)
are functions that take an int parameter. The only difference between them is that the second one cannot modify it's parameter, while the first one can modify it, like any other non-const variable inside the function.
However, there is a difference between
void foo(const int *p)
and
void foo(int *p)
One is a pointer to a const integer, the other one is a pointer to a mutable integer. They are different types.
Bonus answer:
Both
void foo(int *p)
and
void foo(int * const p)
have the same parameter type. Both functions' parameter is a pointer to an int. Except that the second one's parameter is const value, and the function cannot modify it.
Confused yet?
why const is ignored in the first case and not so in the second one?
In the 1st case, const is qualified for the parameter itself, while in the 2nd case, const is qualified for the pointee, not the pointer itself. Const pointer and pointer to const are not the same thing.
In the 2nd case, pointer to const and pointer to non-const are different and acceptable for overloading. If you make the pointer itself const, i.e. int* const p vs int* p, you'll get the same result as the 1st case.
Why can't I take a reference to s2 in foo? I'm compiling with gcc 5.1.0:
#include <cstring>
void foo(const char*& p)
{
while (*p && *p <= '5')
++p;
}
int main()
{
const char *s1 = "1234567890";
char *s2 = new char[strlen(s1) + 1];
strcpy(s2, s1);
foo(s1); // OK
foo(s2); // error
return 0;
}
I compiled with:
$ g++ test_reference.cpp
The compiler gave me:
test_reference.cpp: In function ‘int main()’:
test_reference.cpp:16:11: error: invalid initialization of non-const reference of type ‘const char*&’ from an rvalue of type ‘const char*’
foo(s2); // error
^
test_reference.cpp:3:6: note: initializing argument 1 of ‘void foo(const char*&)’
void foo(const char*& p)
^
For simplicity, you are trying to do:
char* s = ...;
const char*& r = s;
The const here may be misleading. You would think this is equivalent to:
int i = 4;
const int& ri = i;
Which works. But these are not equivalent. ri here is a reference to a const type, but r is a reference to a pointer to const char, that is not a const type.
The ultimate issue is that char* and char const* are not reference-related (meaning that the types are either the same or in the same hierarchy). However, if your reference was a reference to a const type, it would work fine. That is:
const char* const& cr = s;
Basically, you can take an lvalue reference to a non-const type T only from a reference-related type or from a class that is convertible to a reference related type. But you can take an lvalue reference to a const type from a much wider source of expressions:
double d = 4.0;
int& ri = d; // error: int, double aren't reference-related
const int& rci = d; // OK
You can cast it to const reference.
foo((const char *&) s2);
Could you please explain me the meaning of the second "const" in the following expression:
int i = 42;
const int &const ri = i;
Are there some cases where const T &const is mandatory?
What I understood so far:
Pointers are objects, and they can be reassigned.
int i = 42;
const int *ptr1 = &i; // valid: (low-level const): *ptr1 cannot be changed.
int *const ptr2 = &i; // valid: (high-level const): ptr2 cannot be changed.
const int *const ptr3 = &i: // also valid (combination of 2 precedent lines).
References, unliked pointers are not objects (they have no address) and cannot be reassigned ==> no meaning for an "high-level const"
int i = 42;
int &ri1 = i; // valid: ri1 is a new name for i
const int &ri2 = 7; // valid and const is required
const int &ri3 = i; // valid, what is the use of const?
const int &const ri4 = i; // valid, has the second const an importance?
The form int & const is ill-formed (C++11 §8.3.2):
In a declaration T D where D has either of the forms
& attribute-specifier-seqopt D1
&& attribute-specifier-seqopt D1
and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T,” then the type of the identifier of D is “derived-declarator-type-list reference to T.” The optional attribute-specifier-seq appertains to the reference type. Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3) or of a template type argument (14.3), in which case the cv-qualifiers
are ignored.
So no, it can't be mandatory anywhere. And it has no semantics.