Reputation: 1593
Clang,GCC,MSVC have different opinion about conversion of member functions. Who is right ?
https://gcc.godbolt.org/z/QNsgwd
template<typename T>
struct a
{
template <typename... Args>
void va(Args...) {}
template <typename X>
void x(X) {}
void y(int) {}
};
struct b : a<b>
{
void testva()
{
using F = void (a<b>::*)();
F f = (F)&a<b>::va<int>; // gcc: error, msvc: error, clang: ok
}
void testx()
{
using F = void (a<b>::*)();
F f = (F)&a<b>::x<int>;// gcc: error, msvc: ok, clang: ok
}
void testy()
{
using F = void (a<b>::*)();
F f = (F)& a<b>::y; // gcc: ok, msvc: ok, clang: ok
}
};
Upvotes: 4
Views: 147
Reputation: 40811
Let's simplify to this example:
struct a {
template <typename... Args>
void va(Args...) {}
template <typename X>
void x(X) {}
void y(int) {}
};
using no_args = void(a::*)();
using int_arg = void(a::*)(int);
And lets try the following four things:
reinterpret_cast<no_args>(&MEMBER_FUNCTION); // (1)
(no_args) &MEMBER_FUNCTION; // (2)
(no_args) static_cast<int_arg>(&MEMBER_FUNCTION); // (3)
int_arg temp = &MEMBER_FUNCTION; (no_args) temp; // (4)
(Replacing MEMBER_FUNCTION
with &a::va<int>
, &a::x<int>
and &a::y
).
clang compiles all of them.
gcc compiles everything except (2) with &a::va<int>
and &a::x<int>
.
MSVC compiles everything except (1) and (2) with &a::va<int>
(but is fine with &a::x<int>
).
Notice that (3) is essentially the same as (4).
https://gcc.godbolt.org/z/a2qqyo showing an example of this.
What you can see from this is that &MEMBER_FUNCTION
is not resolved to a specific member function pointer in case of templates, but if it is resolved, it is allowed to reinterpret it into another member function pointer type.
What the standard has to say:
A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. A function with type F is selected for the function type FT of the target type required in the context if F (after possibly applying the function pointer conversion) is identical to FT. [ Note: That is, the class of which the function is a member is ignored when matching a pointer-to-member-function type. — end note ] The target can be:
[...]
- an explicit type conversion ([expr.type.conv], [expr.static.cast], [expr.cast])
An example given later on is:
int f(double);
int f(int);
void g() {
(int (*)(int))&f; // cast expression as selector
}
And a few more quotes about templates:
Template arguments can be deduced from the type specified when taking the address of an overloaded function. The function template's function type and the specified type are used as the types of P and A, and the deduction is done as described in [temp.deduct.type].
[...] if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.
This seems like MSVC is right.
&a::va<int>
is not resolved unless you assign/cast it to a void(a::*)(int)
. You should also be able to assign it to void(a::*)(int, char)
or void(a::*)(int, double, char)
, where the Args
would be deduced as { int, char }
and { int, double, char }
respectively. That means that (no_args) &a::va<int>
should fail, as there are many possible sets of Args
it could be (All of them start with int
, and clang overzealously resolves it), and none of them take zero parameters, so (no_args) &a::va<int>
is a static_cast
that should fail.
As for &a::x<int>
, there is only one possible function, so it should work exactly the same way as &a::y
(But gcc still hasn't resolved it yet).
Upvotes: 0
Reputation: 72311
testx
and testy
are well-formed, so gcc is wrong about testx
. But the Standard is somewhat vague about testva
.
Starting with the easiest, in testy
the expression &a<b>::y
names a non-template function which is not overloaded, so it has type void (a<b>::*)(int)
without need for further analysis. Conversion from any pointer-to-member-function to any other pointer-to-member-function is a well-formed reinterpret_cast
with unspecified results except if converted back to the original type, and a C-style cast can do what a reinterpret_cast
can do.
For template functions we have [over.over]/1-2:
A use of an overloaded function name without arguments is resolved in certain contexts to a function, a pointer to function or a pointer to member function for a specific function from the overload set. A function template name is considered to name a set of overloaded functions in such contexts. A function with type
F
is selected for the function typeFT
of the target type required in the context ifF
(after possibly applying the function pointer conversion) is identical toFT
. The target can be
...
an explicit type conversion ([expr.type.conv], [expr.static.cast], [expr.cast]),
...
If the name is a function template, template argument deduction is done ([temp.deduct.funcaddr]), and if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization, which is added to the set of overloaded functions considered. [ Note: As described in [temp.arg.explicit], if deduction fails and the function template name is followed by an explicit template argument list, the template-id is then examined to see whether it identifies a single function template specialization. If it does, the template-id is considered to be an lvalue for that function template specialization. The target type is not used in that determination. — end note ]
So this means we first try template argument deduction for a<b>::x<int>
, matching it to the target type void (a<b>::*)()
. But there are no specializations that can possibly give an exact match, since they all have one argument, not zero, so deduction fails. But per the note, there's also [temp.arg.explicit] (paragraph 3 in C++17, 4 in the latest C++20 draft):
Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. ... In contexts where deduction is done and fails, or in contexts where deduction is not done, if a template argument list is specified and it, along with any default template arguments, identifies a single function template specialization, then the template-id is an lvalue for the function template specialization.
In testx
, the template-id a<b>::x<int>
identifies a single function template specialization. So it names that specialization, and again the C-style cast is valid with unspecified result.
So in testva
, does a<b>::va<int>
identify a single specialization? It would certainly be possible to use that expression to name different specializations, via [temp.arg.explicit]/9:
Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.
Except this says "template argument deduction". And here the template argument deduction involved fails, since it required an impossible match with the target type void (a<b>::*)()
. So nothing really explains whether a<b>::va<int>
identifies a single specialization, since no other method of getting additional template arguments is described, or identifies multiple specializations, since it could be validly used in other contexts with matching target types.
Upvotes: 3
Reputation: 40070
[expr.reinterpret.cast]/10
A prvalue of type “pointer to member of
X
of typeT1
” can be explicitly converted to a prvalue of a different type “pointer to member ofY
of typeT2
” ifT1
andT2
are both function types or both object types. The null member pointer value is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:
- Converting a prvalue of type “pointer to member function” to a different pointer-to-member-function type and back to its original type yields the original pointer-to-member value.
- Converting a prvalue of type “pointer to data member of
X
of typeT1
” to the type “pointer to data member ofY
of typeT2
” (where the alignment requirements ofT2
are no stricter than those ofT1
) and back to its original type yields the original pointer-to-member value.
&a<b>::va<int>
et al. are prvalue of type "pointer to member of a<b>
of type void(int)
" and converting it (without calling the resulting function pointer whose value is unspecified) is legal.
Upvotes: 0