Cannot convert overloaded function pointer to member function pointer? - c++

I have code as following
class A
{
public:
int Key() const;
};
class B : public A
{};
template<class T, int (T::*MemFunction)() const>
class TT
{
public:
int Get() const {return (m_t.*MemFunction)();}
private:
T m_t;
};
TT<B, &B::Key> t; // Wrong, cannot convert overloaded function
// to int (T::*MemFunction)() (VS2010)
Why and how to a similar way works? Thanks

The answer has already been provided by billz, but I will try to produce an explanation.
In C++ when obtaining a pointer to member, the result of the expression is not a pointer to member of the type present in the expression, but rather a pointer-to-member of the type where the member is defined. That is, the expression &B::Key yields &A::Key, as the member function Key is defined in A and not in B. This is defined in §5.3.1/3, which is hard to read but comes with an example:
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. 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 (1.7) or a pointer to the designated function. [ Note: In particular, the address of an object of type “cv T” is “pointer to cv T”, with the same cv-qualification. — end note ] [ Example:
struct A { int i; };
struct B : A { };
... &B::i ... // has type int A::*
— end example ]
This means that your template instantiation is equivalent to:
TT<B, &A::Key>
While a pointer-to-member to a member of a base can be converted to a pointer-to-member to the a derived type, for the particular case of a non-type non-template template parameter that conversion is not allowed. The conversions for non-type non-template template parameters are defined in §14.3.2/5, which for this particular case states:
For a non-type template-parameter of type pointer to member function, if the template-argument is of type std::nullptr_t, the null member pointer conversion (4.11) is applied; otherwise, no conversions apply.
Since the &A::Key cannot be converged to int (B::*)() const, the template instantiation is ill-formed. By adding the cast in the template instantiation you are forcing the conversion to happen before instantiating the template, and the instantiation becomes valid as there is no need for conversiones.

A cast should make it work:
typedef int (B::*Key)() const;
TT<Key(&B::Key)> t;
t.Get();

Related

why a constexpr expression of literal class type with a pointer subobject can't be a non-type template argument

After looking at the post about non-type template argument,I have a confusion for an example in that post,I cite the example here:
struct VariableLengthString {
const char *data_ = nullptr;
constexpr VariableLengthString(const char *p) : data_(p) {}
auto operator<=>(const VariableLengthString&) const = default;
};
template<VariableLengthString S>
int bar() {
static int i = 0;
return ++i;
}
int main() {
int x = bar<"hello">(); // ERROR
}
The post says "the relevant wording is [temp.arg.nontype]/2",So I took a look at that rule,It constrains what can be a non-type template argument.
A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter.
So,I took a look at converted constant expression,Its definitions are here:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only...
What is a constant expression?These rules are here:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
(2.2) an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor.
So,Is the class type VariableLengthString a literal class?Yes,it is.what rules can prove that are here:
A type is a literal type if it is:
1. possibly cv-qualified void; or
2. a scalar type; or
3. a reference type; or
4. an array of literal type; or
a possibly cv-qualified class type that has all of the following properties:
it has a trivial destructor,
it is either a closure type, an aggregate type, or has at least one constexpr constructor or constructor template (possibly inherited from a base class) that is not a copy or move constructor,
if it is not a union, all of its non-static data members and base classes are of non-volatile literal types.
The key point is that,Are these types of sub-objects of ojbects of type VariableLengthString all literal types? Does the class VariableLengthString have at least one constexpr constructor?Yes they are.Because of these rules:
Arithmetic types, enumeration types, pointer types, pointer to member types ([basic.compound]), std​::​nullptr_­t, and cv-qualified versions of these types are collectively called scalar types.
So,the sub-object data_,it's of type pointer.hence,it's scalar type,also a literal type.The bullet 3 is satisfied.Does constexpr VariableLengthString(const char *p) a constexpr constructor?Yes,it is,Because of these rules:
The definition of a constexpr constructor shall satisfy the following requirements:
the class shall not have any virtual base classes;
each of the parameter types shall be a literal type;
every non-variant non-static data member and base class subobject shall be initialized
For constexpr VariableLengthString(const char *p),these three rules are all be satisfied.In summary,the class VariableLengthString is a literal type and a constexpr expression of type VariableLengthString could be used as a non-type template argument,because it satisfies the requirement of being a converted constant expression.why the code above is ill-formed?If I miss something,please help me find out them.
The code is ill-formed because the standard says so:
For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):
...
a string literal
Emphasis added. C++17 and before would not allow you to use a pointer (or reference) to a literal as an NTTP. C++20 therefore doesn't allow you to smuggle a pointer (or reference) to a literal through a class member of an NTTP.

function to pointer reference conversion

I am wondering why the following code is correct:
void foo(){}
void foo2(void(*)()){};
void foo3(void(*)()&){};
int main(){
foo; // type void(&)() lvalue
foo2(foo); // void(&)() -> void(*)() function to pointer conversion
foo3(foo); // ?? conversion
}
According to the function to pointer conversion here
An lvalue of function type T can be converted to a prvalue of type “pointer to T”.
The result is a pointer to the function.
The conversion is fine from the void(&)() to the void(*)(), but not void(*)()&.
What is the reason for this code snippet (especially line with ??) to be correct?
The parameter of foo3 which is void(*)()& is actually a function pointer with a ref-qualifier. This is not allowed by the standard.
The C++17 standard draft n4659 states :
[dcl.fct]/6
A function type with a cv-qualifier-seq or a ref-qualifier (including a type named by typedef-name) shall appear only as:
(6.1) — the function type for a non-static member function,
(6.2) — the function type to which a pointer to member refers,
(6.3) — the top-level function type of a function typedef declaration or alias-declaration,
(6.4) — the type-id in the default argument of a type-parameter, or
(6.5) — the type-id of a template-argument for a type-parameter.
The parameter of foo3 does not meet any of the criteria above. The closest to what you are trying to do is (6.2).
So you can have this instead:
void foo(){}
class C {
public:
void foo1() & {}
};
void foo2(void(*)()){};
void foo3(void(C::*)()&){};
int main(){
(void) foo;
foo2(foo);
foo3(&C::foo1);
}
This compiles on both GCC and Clang.
Demo here.

Passing pointer to base class function as a template argument [duplicate]

What is wrong with this code? I thought I could convert due to this answer:
Is it safe to "upcast" a method pointer and use it with base class pointer?
struct B
{
void f(){}
};
struct D : B
{
virtual ~D(){}
};
template <typename FP, FP fp>
void g()
{
}
int main()
{
g<void (D::*)(), &B::f>();
return 0;
}
Error:
t.cpp:18:27: error: could not convert template argument '&B::f' to 'void (D::*)()'
g<void (D::*)(), &B::f>();
This doesn't work either:
g<void (D::*)(), static_cast<void (D::*)()>(&B::f)>();
This is disallowed by the standard (C++11, [temp.arg.nontype]§5):
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
...
For a non-type template-parameter of type pointer to member function, if the template-argument is of type std::nullptr_t, the null member pointer conversion (4.11) is applied; otherwise, no conversions apply. If the template-argument represents a set of overloaded member functions, the matching member function is selected from the set (13.4).
(Emphasis mine)
Casts are not allowed either, because of [temp.arg.nontype]§1:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a pointer to member expressed as described in 5.3.1.
Where 5.3.1§4 reads:
A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses.
This combines to say that a cast experssion is not allowed as a non-type template argument.
So, while such conversions are possible at runtime, it seems there's no way to use them as template arguments.

what is wrong with this pointer to member conversion?

What is wrong with this code? I thought I could convert due to this answer:
Is it safe to "upcast" a method pointer and use it with base class pointer?
struct B
{
void f(){}
};
struct D : B
{
virtual ~D(){}
};
template <typename FP, FP fp>
void g()
{
}
int main()
{
g<void (D::*)(), &B::f>();
return 0;
}
Error:
t.cpp:18:27: error: could not convert template argument '&B::f' to 'void (D::*)()'
g<void (D::*)(), &B::f>();
This doesn't work either:
g<void (D::*)(), static_cast<void (D::*)()>(&B::f)>();
This is disallowed by the standard (C++11, [temp.arg.nontype]§5):
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
...
For a non-type template-parameter of type pointer to member function, if the template-argument is of type std::nullptr_t, the null member pointer conversion (4.11) is applied; otherwise, no conversions apply. If the template-argument represents a set of overloaded member functions, the matching member function is selected from the set (13.4).
(Emphasis mine)
Casts are not allowed either, because of [temp.arg.nontype]§1:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a pointer to member expressed as described in 5.3.1.
Where 5.3.1§4 reads:
A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses.
This combines to say that a cast experssion is not allowed as a non-type template argument.
So, while such conversions are possible at runtime, it seems there's no way to use them as template arguments.

Derived class type in template argument doesn't compile

This is the code snippet I have been hopelessly stuck on.
template <class T, T nt>
class C;
struct base{
int i;
} b;
struct derived : base{} d;
C<base*,&d> obj;
Why this giving error could not convert template argument &d to base*?
When matching an argument to a parameter that is a pointer/reference, derived to base conversions are not considered even if the conversions are valid in other circumstances.
14.3/5 [Standard quote just for reference]
If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
....
for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the
array-to-pointer conversion (4.2) are applied. [Note: In particular, neither the null pointer conversion
(4.10) nor the derived-to-base conversion (4.10) are applied. Although 0 is a valid template-argument
for a non-type template-parameter of integral type, it is not a valid template-argument for a non-type
template-parameter of pointer type. ]