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
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.
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.
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 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.
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. ]