this in unevaluated context in static member functions - c++

Why this is not allowed in unevaluated context in static member functions?
struct A
{
void f() {}
static void callback(void * self) // passed to C function
{
static_cast< decltype(this) >(self)->f();
}
};
This code gives an error:
error: 'this' is unavailable for static member functions
static_cast< decltype(this) >(self)->f();
^~~~
decltype(this) there is needed for brevity (sometimes it is much more shorter, then VeryVeryLongClassName *), another advantage is the fact that intention is more clear.
What Standard says about using this in unevaluated contexts in static member functions?

I don't see how it matters that this appears within an unevaluated context, you've referred to something that doesn't exist in a static member function, so how is the compiler supposed to deduce the type of this within this context?
As a corollary, the type of this in a non-static member function is dependent on the cv-qualifier of said member function, decltype(this) would yield T const* if the member function were const, and T * if it weren't. Thus, the type is dependent on the context of the expression. In your example, the context has no this pointer.
To alleviate the pain of having to name the class you could add an alias for it.
class VeryVeryLongClassName
{
using self = VeryVeryLongClassName;
};

The current draft of the Standard has some conflicting information.
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type "pointer to cv-qualifier-seq X" wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator.
It shall not appear within the declaration of either a static member function or an explicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).
First it prohibits the expression this in a static member function, but then it says that the type and value category of this are defined within static member functions (and it does so in a way which resolves the question of cv qualification). Which seems useless if there is no way to refer to said this to obtain its type.
A potential resolution would be for the rule to change from "shall not appear" to "shall not be evaluated", which would immediately permit it to appear in unevaluated context such as decltype and sizeof.

Related

std::add_pointer implementation for non-static member functions

This question is a follow-up of A question regarding the implementation of std::add_pointer
Under std::add_pointer
there is the following reference:
Otherwise (if T is a cv- or ref-qualified function type), provides the
member typedef type which is the type T.
Based on reading Non-static member functions: const-, volatile-, and ref-qualified member functions, my understanding is that a for a non-static member function with given cvand/or ref qualification,
a) the cv qualification of the function applies to the this pointer as well, within the scope of the function
b) the ref qualification of the function does not apply to the this pointer within the scope of the function
Given this, why is it that std::add_pointer cannot provide the member typedef type T* in the case of a non-static member function with cv or ref qualification?
Per [dcl.ptr]/4:
[ Note: Forming a pointer to reference type is ill-formed; see
[dcl.ref]. Forming a function pointer type is ill-formed if the
function type has cv-qualifiers or a ref-qualifier; see
[dcl.fct]. Since the address of a bit-field cannot be taken, a
pointer can never point to a bit-field.  — end
note ]
The pointer-to-cv-qualified-function type you are imagining is actually nonexistent. Therefore, std::add_pointer cannot produce such a type :)
A non-static member function type cannot be formed. Such a thing does not exist.
struct T {
int func() const;
};
func does not have a type. You cannot ever use it as an expression on its own.
using mf = int (T::*)() const;
mf myfunc = &T::func;
mf is a pointer to member function type. It is not a function type; pointers to non-static members (including member functions) are data, not functions.
A cv or ref qualification on a member function does not qalify the function type, which does not exist, but the type of the implicit "this" parameter.
The paragraph you quote only applies to non-member or static member functions.

Is decltype of a non-static member function ill-formed?

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.

Is this function pointer with `this` in trailing return type legal?

class C {
auto (*foo)() -> decltype(this);
};
This code is accepted by GCC, MSVC, and clang, but not icc.
Quoting n4140 (roughly C++14) [expr.prim.general]:
3 If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type "pointer to cv-qualifier-seq X" between the optional cv-qualifer-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq and it shall not appear within the declaration of a static member function (although its type and value category are defined within a static member function as they are within a non-static member function). [...]
4 Otherwise, if a member-declarator declares a non-static data member (9.2) of a class X, the expression this is a prvalue of type "pointer to X" within the optional brace-or-equal-initializer. It shall not appear elsewhere in the member-declarator.
Since you're not declaring a member function or member function template, p3 doesn't apply, but this is what would make the code valid for the non-pointer case where you actually declare a member function: the trailing return type is between the optional cv-qualifier-seq and the end of the declarator, as made clearer in the definition of a const member function:
auto foo() const -> decltype(this) { }
p4 is what applies here though. It allows this only to appear in the initialiser. You're putting it elsewhere. p3 doesn't apply, so ICC is correct to reject this.

msvc visual c++ incorrect formation of bound member function expression from static member function

Consider the following code:
#include <type_traits>
struct A {
template<typename T, std::enable_if_t<sizeof(T)<=sizeof(int), int> = 0>
static void fun() {}
template<typename T, std::enable_if_t<(sizeof(T)>sizeof(int)), int> = 0>
static void fun() {}
void test() {
using fun_t = void(*)();
fun_t ff[] = {
fun<int>,
&fun<int>,
A::fun<int>,
&A::fun<int>,
this->fun<int>,
&this->fun<int>, //error C2276: '&': illegal operation on bound member function expression
};
}
};
msvc 2015update3 and 2017rc generate error C2276, which makes no sense. gcc, clang and intel compiler are fine.
The work-around is simple: use any of the other expressions above which should all be equivalent.
However, the wording of the error message is disturbing, and one has to wonder, if a bound member function expression is incorrectly formed for any of the other alternatives.
Any insights into this matter?
This is a nice, dark corner of the standard for the language lawyers to feast upon.
[expr.ref] ¶4.3
If E2 is a (possibly overloaded) member function, function overload resolution is used to determine whether E1.E2 refers to a static or a non-static member function.
If it refers to a static member function and the type of E2 is "function of parameter-type-list returning T", then E1.E2 is an lvalue; the expression designates the static member function. The type of E1.E2 is the same type as that of E2, namely "function of parameter-type-list returning T".
Otherwise, if E1.E2 refers to a non-static member function and the type of E2 is "function of parameter-type-list cv ref-qualifieropt returning T", then E1.E2 is a prvalue. The expression designates a non-static member function. The expression can be used only as the left-hand operand of a member function call. The type of E1.E2 is "function of parameter-type-list cv returning T".
It goes without saying that only pointers to static member functions can be formed by the member access expression (i.e. dot or arrow operator). What is to be noted, though, is that overload resolution determines the "staticness" of a member function. So the question is, how can overload resolution act without an argument list? Compilers seem to differ on their interpretation.
Some compilers may recognize that because the expression is being used to form a pointer to a function, the function must be a static member function or else the program is ill formed, and so exclude non-static member functions from the set of candidate functions.
Other compilers may recognize that because all of the overloads are static, overload resolution can not possibly select a non-static member function, and so skip the step altogether.
Yet other compilers may give up entirely, faced with the instruction to apply overload resolution without any argument list.
This is a related example:
struct A {
static void foo(int) {}
static void bar(int) {}
static void bar(double) {}
};
void test() {
A a;
void(*f)(int) = a.foo; // gcc:OK, msvc:OK, clang:OK
void(*g)(int) = a.bar; // gcc:OK, msvc:OK, clang:ERROR
void(*h)(int) = &a.bar; // gcc:OK, msvc:ERROR, clang:ERROR
}
Here Clang is unable to form a pointer to the int overload of bar using the member access expression, with or without the & address-of operator. MSVC is inconsistent in that it succeeds without the & but not with it. But for the aforementioned reasons it is difficult to say which is conformant. GCC is quite happy in all cases to let the member access expression designate an unresolved overload that is then resolved due to the target type of the initialization ([over.over] ¶1).
It gets even more interesting when SFINAE gets involved:
[temp.over] ¶1
...If, for a given function template, argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template...
Thus, some compilers may exclude SFINAE failures from the set of candidate functions for overload resolution for the purpose of determining the "staticness" of a member function. But again, it is hard to say what is conformant here. This may explain why Clang accepts the example in the original question, but not my bar example. I get the feeling implementors are just trying their best to fill in the blanks here.

Where is the standard wording allowing incomplete types in function declarations, but requiring complete types in function definitions?

I recently found out that the types of parameters in a non-defining function declaration may be of incomplete types. This is very exciting.
class A;
class B {
B(A a); // Legal! Wow!
};
The type is required to be complete only for the definition:
B::B(A a) {}; // error: ‘a’ has incomplete type
I've been trying to pin down the legalese for this, but my searches through C++11 for "[in]complete type" have yielded nothing of much interest, leading me to assume that these semantics are defined through an enigmatic maze of constructions.
Can you help me pin down the standard text that defines the above requirements for the types of function parameters being complete or otherwise, in function declarations vs definitions?
(9.2/10 and 9.4.2/2 give us the requirements for static data member declarations and non-static data member definitions in class definitions.)
See 8.3.5p9, which lays down the exact rules. For a = delete definition, implementations are likely to accept incomplete parameter types too, retroactively (as was determined in a DR resolution by the C++ committee).
In particular, there is no action done on parameters or return values in a non-defining function declaration. Copying of arguments to parameters is done in the context of the caller. And destruction of parameters is done in the context of the callee, in the function definition. Destruction of the return value is done in the context of the caller in a function call except if the call is the topmost expression or right operand of a topmost comma operator in a decltype. Then no destruction happens because no temporary is created as a special case (to help SFINAE libraries).
Function declaration
There doesn't appear to be anything directly addressing this. It may be that it's allowed because it is not disallowed.
7.1.1/9 tells us that it's ok for an extern declaration (which is semantically similar to a member function declaration), and shows us non-normatively that types in such declarations may be incomplete:
[C++11: 7.1.1/9]: The name of a declared but undefined class can be used in an extern declaration. Such a declaration can only be used in ways that do not require a complete class type. [ Example:
struct S;
extern S a;
extern S f();
extern void g(S);
void h() {
g(a); // error: S is incomplete
f(); // error: S is incomplete
}
—end example ]
Function definition (thanks litb)
[C++11: 8.3.5/9]: Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within
the class).
Function call
[C++11: 5.2.2/4]: [..] When a function is called, the parameters that have object type shall have completely-defined object type. [ Note: this still allows a parameter to be a pointer or reference to an incomplete class type. However, it prevents a passed-by-value parameter to have an incomplete class type. —end note ] [..]