Interpretation of access decoration of member functions [duplicate] - c++

This question already has answers here:
What does the single ampersand after the parameter list of a member function declaration mean?
(3 answers)
Const reference qualifier on a member function [duplicate]
(2 answers)
What is "rvalue reference for *this"?
(3 answers)
Closed 8 months ago.
In C++11 and later, one can decorate the member function with &, const&, oe && (or other combinations).
If one has several overloads, and at least one is specified like this, the others must follow the same convention.
Pre C++11 style:
struct A {
void f() const {} // #1
void f() {} // #2
};
C++11 and later:
struct {
void f() const& {} // #3
void f() & {} // #4
// void f() && {} // if necessary
};
Until today, I though that #4 was equivalent to #2, but today I found a counter example:
struct A {
void f() & {}
// void f() && {} // commented for testing purposes, see below
}
struct B {
void f() {}
}
...
A{}.f(); // compile error: argument discards qualifiers
B{}.f(); // ok
https://godbolt.org/z/qTv6hMs6e
So, what is the deal? An undecorated (non-const) member function written in the old style is equivalent to both its && or & version depending on the context (at the calling point)?
Is the below code the correct interpretation?
struct B {
void f() {... some body...}
}
...is the same as this?
struct B {
void f() & {... some body...}
void f() && {... some (same) body...}
}
... which is the same as this:
struct B {
void f() & {... some body...}
void f() && {return f();}
}

The qualifiers have the exact same meaning as if they were the qualifiers on the hypothetical implicit object parameter which is passed the object expression of the member access expression.
So, #4 can not be called on a prvalue, because a non-const lvalue reference can not bind to a prvalue, explaining why A{}.f(); doesn't work. (A{} is a prvalue)
The old style without reference qualifier is the odd one. It behaves in overload resolution as if the implicit object parameter was an lvalue reference (const or not depending on that qualifier), but in contrast to normal function parameters it is allowed to bind to rvalues anyway for the purpose of overload resolution.
So to replicate the old style unqualified behavior, you need to specify both the &-qualified overload and the &&-qualified overload (at least if the function is not also const-qualified). (There are likely some corner cases where the two qualified member functions are not 100% equivalent to one unqualified one though. I guess a simple example would be trying to take the address &B::f.)

Related

What can be done to prevent misleading assigment to returned value?

After many years of using C++ I realized a quirk in the syntax when using custom classes.
Despite being the correct language behavior it allows to create very misleading interfaces.
Example here:
class complex_arg {
double r_;
double phi_;
public:
std::complex<double> value() const {return r_*exp(phi_*std::complex<double>{0, 1});}
};
int main() {
complex_arg ca;
ca.value() = std::complex<double>(1000., 0.); // accepted by the compiler !?
assert( ca.value() != std::complex<double>(1000., 0.) ); // what !?
}
https://godbolt.org/z/Y5Pcjsc8d
What can be done to the class definition to prevent this behavior?
(Or at the least flag the user of the clas that the 3rd line is not really doing any assignment.)
I see only one way out, but it requires modifying the class and it doesn't scale well (to large classes that can be moved).
const std::complex<double> value() const;
I also tried [[nodiscard]] value() but it didn't help.
As a last resort, maybe something can be done to the returned type std::complex<double> ? (that is, assuming one is in control of that class)
Note that I understand that sometimes one might need to do (optimized) assign to a newly obtained value and passe it to yet another function f( ca.value() = bla ). I am not questioning this usage per se (although it is quite confusing as well); I have the problem mostly with ca.value() = bla; as a standalone statement that doesn't do what it looks.
Ordinarily we can call a member function on an object regardless of whether that object's value category is an lvalue or rvalue.
What can be done to the class definition to prevent this behavior?
Prior to modern C++ there was no way prevent this usage. But since C++11 we can ref-qualify a member function to do what you ask as shown below.
From member functions:
During overload resolution, non-static member function with a cv-qualifier sequence of class X is treated as follows:
no ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified X and is additionally allowed to bind rvalue implied object argument
lvalue ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified X
rvalue ref-qualifier: the implicit object parameter has type rvalue reference to cv-qualified X
This allows us to do what you ask for a custom managed class. In particular, we can & qualify the copy assignment operator.
struct C
{
C(int)
{
std::cout<<"converting ctor called"<<std::endl;
}
C(){
std::cout<<"default ctor called"<<std::endl;
}
C(const C&)
{
std::cout<<"copy ctor called"<<std::endl;
}
//-------------------------v------>added this ref-qualifier
C& operator=(const C&) &
{
std::cout<<"copy assignment operator called"<<std::endl;;
return *this;
}
};
C func()
{
C temp;
return temp;
}
int main()
{
//---------v---------> won't work because assignment operator is & qualified
func() = 4;
}

Using member function without taking address? [duplicate]

This question already has answers here:
Pointer to member function - syntax
(2 answers)
Closed last year.
class C{
public:
int i(){return 3;}
};
void memfn(int (C::* const & func)()){}
void fn(int (* const & func)()){}
int main() {
fn(&foo); //Works
fn(foo); //Works
memfn(&C::i); //Works
memfn(C::i); //Doesn't Work
}
When passing a function pointer as a parameter, the address-of operator is optional on the function. Why does it not work if removed on a member function?
There is an implicit conversion from global function references, or static member function references, or non-capturing lambdas, to function pointers. This is for capability with C.
But such implicit reference-to-pointer conversions are not for non-static member functions (and capturing lambdas!), because they need an implicitly passed this pointer to work, the conversion just lacks that thing.
In your case, if we make i() static, then it can be passed to fn either by reference or by address.

What type is int(int)& or int(int) const &?

std::is_function is specialized for types which have signature similar to:
int(int) &
see here:std::is_function
But this is neither a pointer to a member method, which signature could be:
int(T::*)(int) &
Nor it can be a reference to a function:
int (&)(int)
So what is this strange signature?
It's a function type which only exists in the type system. It cannot ever be created.
But this is neither a pointer to a member method, which signature could be:
int(T::*)(int) &
It's this, without the pointer. The type system allows you to describe that as a type.
#include <type_traits>
struct T { };
using A = int(int) &;
using B = A T::*;
using C = int(T::*)(int) &;
static_assert(std::is_same_v<B, C>);
#T.C. mentions PR0172R0, which discusses how the presence of these types causes issues for library writers, and suggests several options which might reduce those issues. One of the options is getting rid of them entirely, others reduce their impact. Depending on how this goes, this answer may or may not be correct for future versions of C++.
On the documentation page you link to, you'll see this comment:
// specialization for function types that have ref-qualifiers
above the list the examples you reference come from.
Those are functions with ref-qualifiers, which you can read more about here.
In short, they are similar to const qualified functions. Here's an example:
struct foo
{
void bar() & { std::cout << "this is an lvalue instance of foo" << "\n"; }
void bar() && { std::cout << "this is an rvalue instance of foo" << "\n"; }
};
int main(int argc, char* argv[])
{
foo f{};
f.bar(); // prints "this is an lvalue instance of foo"
std::move(f).bar(); // prints "this is an rvalue instance of foo"
return 0;
}
I can't think of a great use case for this feature, but it is possible to use.
Since the beginning of times (referring to the first C++ standard) you could declare such "strange" function types as, for example
typedef int F() const;
Despite the fact that the above declaration does not immediately involve any classes, the trailing const in this case can only serve as const-qualification of a non-static class member function. This restricts the usage of the above typedef-name to class member declarations. For example, one could use it as follows
struct S {
F foo; // Declares an `int S::foo() const` member function
};
int S::foo() const { // Defines it
return 42;
}
F S::*p = &S::foo; // Declares 'p' as `int (S::*)() const` pointer
Note that, however obscure, this is a "classic" C++ feature that's been in the language for a long time.
What you have in your example is effectively the same thing, but with C++11 ref-qualifier in place of const qualifier.

Invalid object after elision of copy operation? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why is it an error to use an empty set of brackets to call a constructor with no arguments?
Most vexing parse: why doesn't A a(()); work?
This one gets me mad. Maybe its just too simple.
struct Foo
{
Foo() {}
Foo(const Foo& f) {}
void work() {}
};
int main()
{
Foo f( Foo() );
f.work();
}
GCC 4.6 gives me:
error: request for member ‘work’ in ‘f’, which is of non-class type ‘Foo(Foo (*)())’
After elision of the copy operation the effective code might look like:
int main()
{
Foo f;
f.work();
}
But why can't i call work() ??
Edit:
Yes, duplicate (see below). Didn't find the original post when search first because the source of the symptoms of this is located where i didn't expect that.
Because Foo f( Foo() ); is a function declaration.
I think you want: Foo f;
Or in case you want to copy-construct:
Foo f( (Foo()) );
f is effectively a function declaration within main function.
Try
Foo f((Foo())); // to make the definition of f explicit enough.
n3337 8.2
The ambiguity arising from the similarity between a function-style
cast and a declaration mentioned in 6.8 can also occur in the context
of a declaration. In that context, the choice is between a function
declaration with a redundant set of parentheses around a parameter
name and an object declaration with a function-style cast as the
initializer. Just as for the ambiguities mentioned in 6.8, the
resolution is to consider any construct that could possibly be a
declaration a declaration. [ Note: A declaration can be explicitly
disambiguated by a nonfunction-style cast, by an = to indicate
initialization or by removing the redundant parentheses around the
parameter name. — end note ] [ Example:
struct S {
S(int);
};
void foo(double a) {
S w(int(a));
//function declaration
S x(int());
//function declaration
S y((int)a);
//object declaration
S z = int(a);
//object declaration
}
— end example ]
C++ parser interprets Foo f(Foo()); expression as the function declaration with the signature Foo(Foo(*)()), i.e. a function returning Foo and taking a function pointer to the function returning Foo. Adding explicit parenthesis around the argument like so Foo f((Foo())); will resolve the ambiguity. But consider actually just doing Foo f; which avoids redundant code.

A quick c++ query [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Overloading by return type
Why can't I declare three member functions like this:
void x(int a);
void x(String a);
int x(String a);
?
Because you can't overload by return type.
void x(string a)
and
int x(string a)
have the same signature. The signature is made of:
function name
parameters
cv-qualifiers
Which, in your case, are the same.
C++ does not allow you to overload functions based on the return type. Function overloading is only allowed based on the type of the arguments. This means that void x(String a) and int x(String a) are seen as the same function as far as overloading rules are concerned.
One important case that may be confusing (but is often used) is when const is put at the end of a member function. That would look something like int number_of_peanuts(bool tasty_only) const. That const at the end means that the class that this member function is part of cannot be modified by this function.
However, this is actually just a special case of argument type overloading. When you have a member function that is not declared static, there is implicitly an extra parameter added to your function, this this pointer. This means that the example function I gave is roughly equivalent to int number_of_peanuts(Class const * this, bool tasty_only). If you didn't have const at the end of the function, then instead it would be like int number_of_peanuts(Class * this, bool tasty_only).
So to summarize, the type and number of arguments are the only thing that allow you to overload. If you pass by value, as in void x(int a), then const won't give you overload opportunities, because the outside world cannot tell the difference between whether you modify your copy or not. If you pass by reference or pass a pointer, then you can use const with the thing they are referring to as overload options, so void x(std::string & a) and void x(std::string const & a) are different, because the world can tell if you modify a or not. Putting const at the end of the function is another source of overload opportunity. And finally, and most obviously, void x(int a) and void x(int a, int b) is a legal overload because you have a different number of arguments.