Consider the following code :
#include <iostream>
class Test
{
public:
Test() : a{ 0 }
{}
void print() const
{
std::cout << "a : " << a << std::endl;
}
void operator->()
{
a = 5;
}
void operator++()
{
++a;
}
public:
int a;
};
int main()
{
Test a;
a.print();
// Increment operator
a.operator++(); // CORRECT
++a; // CORRECT
a.print();
// Indirection operator
a.operator->(); // CORRECT
a->; // INCORRECT
a.print();
}
Why is the call to the second -> operator incorrect? I know this usage of -> is different from the general usage, but is such usage disallowed by the standard?
The sub-section on Class member access from Overloaded Operators from CPP standard draft (N4713) states this:
16.5 Overloaded operators
...
16.5.6 Class member access [over.ref]
1. operator-> shall be a non-static member function taking no parameters. It implements the class member access syntax that uses ->.
postfix-expression -> template(opt) id-expression //This!!
postfix-expression -> pseudo-destructor-name
An expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (16.3).
As you can see the id-expression is very much needed if the -> operator is overloaded.
Class member access [expr.ref/1] §7.6.1.5/1:
A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template ([temp.names]), and then followed by an id-expression, is a postfix expression. The postfix expression before the dot or arrow is evaluated; the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.
Names [expr.prim.id] (§7.5.4):
id-expression:
unqualified-id
qualified-id
An id-expression is a restricted form of a primary-expression. [ Note: An id-expression can appear after . and -> operators. — end note ]
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
2.1. as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or
2.2. to form a pointer to member ([expr.unary.op]), or
2.3. if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
As said by M.M, a->; is a sintax error. A non-static member operator function without argument is a prefix (as operator++(); postfix is operator++(int)) a.operator->(); would be ->a; (wow!), but this is again a sintax error. The standard specifies the fine details...
Related
I'm trying to understand the consistency in the error that is thrown in this program:
#include <iostream>
class A{
public:
void test();
int x = 10;
};
void A::test(){
std::cout << x << std::endl; //(1)
std::cout << A::x << std::endl; //(2)
int* p = &x;
//int* q = &A::x; //error: cannot convert 'int A::*' to 'int*' in initialization| //(3)
}
int main(){
const int A::* a = &A::x; //(4)
A b;
b.test();
}
The output is 10 10. I labelled 4 points of the program, but (3) is my biggest concern:
x is fetched normally from inside a member function.
x of the object is fetched using the scope operator and an lvalue to the object x is returned.
Given A::x returned an int lvalue in (2), why then does &A::x return not int* but instead returns int A::*? The scope operator even takes precedence before the & operator so A::x should be run first, returning an int lvalue, before the address is taken. i.e. this should be the same as &(A::x) surely? (Adding parentheses does actually work by the way).
A little different here of course, the scope operator referring to a class member but with no object to which is refers.
So why exactly does A::x not return the address of the object x but instead returns the address of the member, ignoring precedence of :: before &?
Basically, it's just that the syntax &A::x (without variation) has been chosen to mean pointer-to-member.
If you write, for example, &(A::x), you will get the plain pointer you expect.
More information on pointers-to-members, including a note about this very property, can be found here.
The C++ standard doesn't explicitly specify operator precedence; it's possible to deduce operator precedence implicitly from the grammar rules, but this approach fails to appreciate the occasional special case like this which doesn't fit into a traditional model of operator precedence.
[expr.unary.op]/3:
The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a
qualified-id naming a non-static or variant member m of some class C with type T, the result has type 'pointer to member of class C of type
T' and is a prvalue designating C::m. Otherwise, if the type of the expression is T, the result has type 'pointer to T' and is a prvalue that is the address of the designated object or a pointer to the designated function.
/4:
A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses. [ Note:
that is, the expression &(qualified-id), where the qualified-id is enclosed in parentheses, does not form an expression of type 'pointer to member'.
[expr.prim.general]/9:
A nested-name-specifier that denotes a class, optionally followed by the keyword template, and then followed by the name of a member of either that class or one of its base classes, is a qualified-id.
What it all adds up to is that an expression of the form &A::x has the type "pointer to member x of class A" if x is a non-static member of a non-union class A, and operator precedence has no influence on this.
I'm not sure to perfectly understand [dcl.type]/4.3:
For an expression e, the type denoted by decltype(e) is defined as follows:
[...]
(4.3) otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
[...]
For me, the emphasized part both apply to id-expression and class member access, right?
Playing with my favorite compiler, I get the following.
✓ Accepted by the compiler
namespace N { void f() {} }
using type = decltype(N::f);
type* pf = N::f;
Ok I guess; N::f is an unparenthesized id-expression and does not name a set of overloaded functions.
✗ Rejected by the compiler
namespace N { void f() {} void f(int) {} }
using type = decltype(N::f); // error: decltype cannot resolve address of overloaded function
type* pf = N::f;
Ok; N::f does name a set of overloaded functions.
✗ Rejected by the compiler
struct S { void f(){} };
using type = decltype(S::f); // error: invalid use of non-static member function 'void S::f()'
type* pf = &S::f;
Hum? S::f would name a set of one overloaded function?
All in all, is my understanding of [dcl.type]/4.3 just bad? is gcc trunk wrong? both? none? kamoulox?
The simple reason is that the use of S::f is constrained for class members.
[expr.prim.id]
2 An id-expression that denotes a non-static data member or
non-static member function of a class can only be used:
as part of a class member access in which the object expression refers to the member's class or a class derived from that class, or
to form a pointer to member ([expr.unary.op]), or
if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
The last bullet, the one related to your code, only applies to non-static data member. There is no provision for functions.
I can only speculate as to why it's not allowed, though I previously asked that question.
It's worth a note that decltype(&S::f) works here as pointer-to-member-function type,
again unless the f names a set of overloaded (member) functions.
The function type itself can be extracted from the pointer-to-member-function type.
If the member function is cv-or-ref qualified then it has an abominable function type.
The std traits are lacking here - a library like Boost.CallableTraits helps.
Stroustrup writes :
Consider a binary operator #. If x is of type X and y is of type Y, x#y is resolved like this:
• If X is a class, look for operator# as a member of X or as a member of a base of X; and
• look for declarations of operator# in the context surrounding x#y; and
• if X is defined in namespace N, look for declarations of operator# in N; and
• if Y is defined in namespace M, look for declarations of operator# in M.
Declarations for several operator#s may be found and overload resolution rules (§12.3) are used to find the best match, if any. This lookup mechanism is applied only if the operator has at least one operand of a user-defined type. Therefore, user-defined conversions (§18.3.2, §18.4) will be considered.
Note that a type alias is just a synonym and not a separate user-defined type (§6.5).
Unary operators are resolved analogously.
Note that in operator lookup no preference is given to members over nonmembers. This differs from lookup of named functions
So what means the bold expression. If class has member and in the same time there is non-member function which could be used in the context, then no preference is given to member? For example
class A
{
public:
bool operator==(const A&)
{
return true;
}
};
bool operator==(const A&, const A&)
{
return true;
}
int main()
{
A a, b;
a == b;
}
I think(and compilers agree with me :) ) that member function should be called, but as written above no preference should be given to member operator. So what exactly means Stroustrup by that sentence?
Your code only compiles because your operators are not equivalent in this context. One is better than the other, according to the rules of implicit conversion sequence ranking.
Note that your member operator lacks const-qualification for its first (left) argument. It actually stands for bool operator==(A&, const A&).
Since your a and b in main are not declared as const, the implicit conversion sequence for the left-hand operand is shorter (better) for member operator. It does not require a qualification conversion of a from A to const A. This is what makes it a better candidate. This is why your member function version is selected.
To make both versions equivalent you have to add const to your member operator declaration
class A
{
public:
bool operator==(const A&) const
{
return true;
}
};
and that will immediately make your a == b comparison ambiguous.
I was trying to validate this statement (my emphasis) in paragraph §5.1.1/8 (page 87) of the C++11 Standard
A nested-name-specifier that denotes a class, optionally followed by
the keyword template (14.2), and then followed by the name of a member
of either that class (9.2) or one of its base classes (Clause 10), is
a qualified-id; 3.4.3.1 describes name lookup for class members that
appear in qualified-ids. The result is the member. The type of the
result is the type of the member. The result is an lvalue if the
member is a static member function or a data member and a prvalue
otherwise.
with the following snippet:
#include <iostream>
namespace N {
class A {
public:
int i;
void f();
};
}
int main()
{
std::cout << &N::A::f << '\n';
std::cout << &N::A::i << '\n';
}
clang and gcc compile this code and VS2013 requires the definition of the member function f.
All three of them print
1
1
but I have no idea where these numbers come from.
live example
According to the paragraph highlighted above the expressions N::A::f is a prvalue, as f is not a static member function. Nonetheless, I was able to take its address in the code.
At the same time, in §5.3.1/3 one reads (emphasis mine):
The result of the unary & operator is a pointer to its operand. The
operand shall be an lvalue or a qualified-id. If the operand is a
qualified-id naming a non-static member m of some class C with type T,
the result has type “pointer to member of class C of type T” and is a
prvalue designating C::m.
which gives the impression that neither N::A::f nor N::A::i are lvalues, as they are qualified-ids.
but I have no idea where these numbers come from.
Pointer-to-members aren't pointers. No operator<< can output their original value, the best and only match is the one that outputs bool values. Thus they are converted to bool (which obviously yields true) and the output is 1. Try to insert std::boolalpha and check the output again.
Nonetheless, I was able to take its address in the code.
How is that a surprise to you? You quoted the part that allows and explains this exact construct. It clearly states that taking the adress of a qualified-id that names a non-static member designates that member.
qualified-ids are not only lvalues or rvalues. It completely depends on context. If they designate non-static members from outside that members class or any subclass of it, they have to be prvalues as they don't designate any specific object but rather a value (or information, in other words -- the type and an offset).
The following simple code example causes some doubts for me:
#include <iostream>
using namespace std;
struct A
{
int a;
A(int a)
{
A::a = a; //It is unclear, because in that case we're applying
//scope resolution operator to A and does.
this -> a = a; //It is clear, because this points to the current object.
}
};
int main()
{
A a(4);
cout << a.a;
}
demo
I know that the section 3.4.3.1/3 says:
A class member name hidden by a name in a nested declarative region or
by the name of a derived class member can still be found if qualified
by the name of its class followed by the :: operator.
But it doesn't specify that the name looked up with "Qualified name lookup" (e.g. A::a in my case) inside the member function shall denote a member of current object.
I'm looking for relevant reference in the Standard.
When searching for something specific to nonstatic class member functions, you should look first at the subclause governing...nonstatic class member functions. §9.3.1 [class.mfct.non-static]/p3:
When an id-expression (5.1) that is not part of a class member access
syntax (5.2.5) and not used to form a pointer to member (5.3.1) is
used in a member of class X in a context where this can be used
(5.1.1), if name lookup (3.4) resolves the name in the id-expression
to a non-static non-type member of some class C, and if either the
id-expression is potentially evaluated or C is X or a base class of X,
the id-expression is transformed into a class member access expression
(5.2.5) using (*this) (9.3.2) as the postfix-expression to the left of
the . operator. [ Note: If C is not X or a base class of X, the class
member access expression is ill-formed. —end note ] Similarly during
name lookup, when an unqualified-id (5.1) used in the definition of a
member function for class X resolves to a static member, an enumerator
or a nested type of class X or of a base class of X, the
unqualified-id is transformed into a qualified-id (5.1) in which the
nested-name-specifier names the class of the member function.
C++.11 §5.1.1¶8
A nested-name-specifier that denotes a class, optionally followed by the keyword template (14.2), and then followed by the name of a member of either that class (9.2) or one of its base classes (Clause 10), is a qualified-id; 3.4.3.1 describes name lookup for class members that appear in qualified-ids. The result is the member. The type of the result is the type of the member. The result is an lvalue if the member is a static member function or a data member and a prvalue otherwise.