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.
Related
I want to know what happens if we have a function parameter that is a reference to a const function as shown below.
Version 1
int anotherFunc()
{
std::cout<<"inside anotherFunc"<<std::endl;
return 5;
}
void func(decltype(anotherFunc) const &someFunction)//note the const here
{
std::cout<<"inside func"<<std::endl;
std::cout<<someFunction()<<std::endl;
}
int main()
{
std::cout << "Hello World" << std::endl;
func(anotherFunc);
return 0;
}
Version 2
int anotherFunc()
{
std::cout<<"inside anotherFunc"<<std::endl;
return 5;
}
void func(decltype(anotherFunc) &someFunction)//note the missing const here
{
std::cout<<"inside func"<<std::endl;
std::cout<<someFunction()<<std::endl;
}
int main()
{
std::cout << "Hello World" << std::endl;
func(anotherFunc);
return 0;
}
My questions are:
Are version 1 and version 2 completely equivalent in terms of the function parameter someFunction of the function func? That is adding const for the function paramter someFunction does nothing(i.e. simply ignored).
If const is ignored in these examples then at what point(document) does the C++ standard specify that const will be ignored for this case.
PS: Looking at the generated assembly it does seem that const is ignored for reference to function parameter.
Yes, the const qualifier is ignored when added to an alias for a function type.
From the standard, [dcl.fct]/7:
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored.
[Note 4: A function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. — end note]
[Example 4:
typedef void F();
struct S {
const F f; // OK: equivalent to: void f();
};
— end example]
Are version 1 and version 2 completely equivalent in terms of the function parameter someFunction of the function func?
Yes. There is no such thing as const qualified function type, and as such no thing as reference to const function. If you were to apply const to explicitly written reference to function type, then the program would be ill-formed. But when the const is applied to a type alias or type deduction, then the const is ignored.
does the C++ standard specify that const will be ignored for this case.
Yes.
I got to know that to make a friend function, friend function should be explicitly declared in enclosing scope or take an argument of its class. However, this one seems to be a caveat, I am failed to understand. Why does the call to f1(99) not works?
class X {
public:
X(int i) {
std::cout << "Ctor called" << std::endl;
}
friend int f1(X&);
friend int f2(const X&);
friend int f3(X);
};
int f1(X& a) {
std::cout << "non-const called" << std::endl;
}
int f2(const X& a) {
std::cout << "const called" << std::endl;
}
int f3(X a) {
std::cout << "object called" << std::endl;
}
int main() {
f1(99);
f2(99);
f3(99);
}
You're calling the functions with argument 99; but all the functions expect an X. 99 could convert to X implicitly, but the converted X is a temporary object and can't be bound to lvalue-reference to non-const.
The temporary object could be bound to lvalue-reference to const (and also rvalue-reference since C++11), then f2(99); works. And it could be copied to the parameter of f3, then f3(99) works too.
The effects of reference initialization are:
Otherwise, if the reference is lvalue reference to a non-volatile const-qualified type or rvalue reference (since C++11):
Otherwise, object is implicitly converted to T. The reference is bound to the result of the conversion (after materializing a temporary) (since C++17). If the object (or, if the conversion is
done by user-defined conversion, the result of the conversion
function) is of type T or derived from T, it must be equally or less
cv-qualified than T, and, if the reference is an rvalue reference, must not be an lvalue (since C++11).
From Latest C++ ISO Standart
https://timsong-cpp.github.io/cppwp/expr#basic.lval-10
An lvalue is modifiable unless its type is const-qualified or is a function type. [ Note: A program that attempts to modify an object through a nonmodifiable lvalue or through an rvalue is ill-formed
But the following code below which modifies temporarily through rvalue runs well.
https://godbolt.org/z/L9H06i
#include <iostream>
struct A
{
std::string s1;
A():s1("123") {}
A&& modify() { s1 = "123411111111111111111111111111111111111111111111111111111111111111111111";
return std::move(*this);
} //modifying temporary object through rvalue!
};
void f(A&& o)
{
std::cout << o.s1.c_str();
}
int main()
{
f(A().modify());//modifying temporary object through rvalue!
return 0;
}
I can't remember which talk it was, but recently I watched some talks from CppCon 2017 and there someone mentioned as some kind of side-note, that the only true way of overloading operator= would be in the following fashion:
class test {
public:
test& operator=(const test&) &;
};
He explicitly emphasized the trailing & but didn't say what it does.
So what does it do?
Ref-qualifiers - introduced in C++11
Ref-qualifiers is not C++17 feature (looking at the tag of the question), but was a feature introduced in C++11.
struct Foo
{
void bar() const & { std::cout << "const lvalue Foo\n"; }
void bar() & { std::cout << "lvalue Foo\n"; }
void bar() const && { std::cout << "const rvalue Foo\n"; }
void bar() && { std::cout << "rvalue Foo\n"; }
};
const Foo&& getFoo() { return std::move(Foo()); }
int main()
{
const Foo c_foo;
Foo foo;
c_foo.bar(); // const lvalue Foo
foo.bar(); // lvalue Foo
getFoo().bar(); // [prvalue] const rvalue Foo
Foo().bar(); // [prvalue] rvalue Foo
// xvalues bind to rvalue references, and overload resolution
// favours selecting the rvalue ref-qualifier overloads.
std::move(c_foo).bar(); // [xvalue] const rvalue Foo
std::move(foo).bar(); // [xvalue] rvalue Foo
}
Note that an rvalue may be used to initialize a const lvalue reference (and in so expanding the lifetime of the object identified by the rvalue), meaning that if we remove the rvalue ref-qualifier overloads from the example above, then the rvalue value categories in the example will all favour the remaining const & overload:
struct Foo
{
void bar() const & { std::cout << "const lvalue Foo\n"; }
void bar() & { std::cout << "lvalue Foo\n"; }
};
const Foo&& getFoo() { return std::move(Foo()); }
int main()
{
const Foo c_foo;
Foo foo;
// For all rvalue value categories overload resolution
// now selects the 'const &' overload, as an rvalue may
// be used to initialize a const lvalue reference.
c_foo.bar(); // const lvalue Foo
foo.bar(); // lvalue Foo
getFoo().bar(); // const lvalue Foo
Foo().bar(); // const lvalue Foo
std::move(c_foo).bar(); // const lvalue Foo
std::move(foo).bar(); // const lvalue Foo
}
See e.g. the following blog post for for a brief introduction:
Andrzej's C++ blog - Ref-qualifiers
rvalues cannot invoke non-const & overloads
To possibly explain the intent of your recollected quote from the CppCon talk,
"... that the only true way of overloading operator= ..."
we visit [over.match.funcs]/1, /4 & /5 [emphasis mine]:
/1 The subclauses of [over.match.funcs] describe the set of candidate functions and the argument list submitted to overload
resolution in each context in which overload resolution is used. ...
/4 For non-static member functions, the type of the implicit object parameter is
(4.1) — “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
(4.2) — “rvalue reference to cv X” for functions declared with the && ref-qualifier
where X is the class of which the function is a member and cv is the
cv-qualification on the member function declaration. ...
/5 ... For non-static member functions declared without a ref-qualifier, an additional rule applies:
(5.1) — even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as
in all other respects the argument can be converted to the type of the
implicit object parameter. [ Note: The fact that such an argument is
an rvalue does not affect the ranking of implicit conversion
sequences. — end note ]
From /5 above, the following overload (where the explicit & ref-qualifier has been omitted)
struct test
{
test& operator=(const test&) { return *this }
}
allows assigning values to r-values, e.g.
int main()
{
test t1;
t1 = test(); // assign to l-value
test() = t1; // assign to r-value
}
However, if we explicitly declare the overload with the & ref-qualifier, [over.match.funcs]/5.1 does not apply, and as long we do not supply an overload declared with the && ref-qualifier, r-value assignment will not be allowed.
struct test
{
test& operator=(const test&) & { return *this; }
};
int main()
{
test t1;
t1 = test(); // assign to l-value
test() = t1; // error [clang]: error: no viable overloaded '='
}
I won't place any opinion as to whether explicitly including the & ref-qualifier when declaring custom assignment operator overloads is "the only true way of overload operator=", but would I dare to speculate, then I would guess that the intent behind such a statement is the exclusion of to-r-value assignment.
As a properly designed assignment operator should arguably never be const (const T& operator=(const T&) const & would not make much sense), and as an rvalue may not be used to initialize a non-const lvalue reference, a set of overloads for operator= for a given type T that contain only T& operator=(const T&) & will never proviade a viable overload that can be invoked from a T object identified to be of an rvalue value category.
As per http://en.cppreference.com/w/cpp/language/member_functions
the & following your member function declaration is lvalue ref-qualifier.
In other words, it requires this to be an l-value (the implicit object parameter has type lvalue reference to cv-qualified X).
There is also &&, which requires this to be an r-value.
To copy from documentation (const-, volatile-, and ref-qualified member functions):
#include <iostream>
struct S {
void f() & { std::cout << "lvalue\n"; }
void f() &&{ std::cout << "rvalue\n"; }
};
int main(){
S s;
s.f(); // prints "lvalue"
std::move(s).f(); // prints "rvalue"
S().f(); // prints "rvalue"
}
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.