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.
Related
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.
At the moment I have a class defined similar to this:
class dummy{
public:
dummy(void(&func)(int))
: member{func}{}
void(&member)(int);
};
but I want to have member defined as a const function reference. I'm not sure exactly how to write this or if it is even possible.
P.S. PLEASE don't recommend me std::function I'm not oblivious to it's existence and have no objection to it, I just want to know whether something like this is doable.
The syntax:
Syntactically, you can achieve this through a type alias (or typedef):
using function_t = void(int); // or typedef void (function_t)(int);
class dummy {
public:
const function_t& member;
dummy(const function_t& func)
: member{func}{}
};
The semantics:
However, the "correct syntax" doesn't buy you anything: dummy is the same as
class dummy {
public:
function_t& member;
dummy(function_t& func)
: member{func}{}
};
(which, in turn is the same as the OP's definition) because the const qualifiers are ignored as per C++11 8.3.5/6:
The effect of a cv-qualifier-seq in a function declarator is not the same as adding cv-qualification on top of the function type. In the latter case, the cv-qualifiers are ignored. [ Note: a function type that has a cv-qualifier-seq is not a cv-qualified type; there are no cv-qualified function types. —end note ]
What about rvalues?
As far as I undestand (from a comment to user657267's answer) the motivation for taking the argument as reference to const was to enable passing rvalues (temporaries):
void f(int) { }
function_t make_function() { return f; }
dummy d1(f);
dummy d2(make_function());
However, this doesn't compile, not because of dummy, but because make_function returns a function which is forbiden by C++11 8.3.5/8
If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed.99 Functions shall not have a return type of type array or function, although they may have a return type of type pointer or reference to such things. There shall be no arrays of functions, although there can be arrays of pointers to functions.
A natural "solution" would be returning a reference:
function& make_function() { return f; }
or
function&& make_function() { return f; }
In both cases, the type of the expression make_function() is an lvalue (which defies the purpose of dummy using reference to const to enable passing rvalues) as per C++11 5.2.2/10
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
Actually, value category is not an issue here. With any legal declaration of make_function and any definition of dummy seem above the declarations of d1 and d2 work fine.
Nevertheless, the mentioned comment to user657267's answer talks about passing a lambda but a reference to function cannot bind to lambda expression because it has a different type:
dummy d3([](int){}); // Error!
The suggested solution
Instead of references use pointers:
using function_t = void(int); // or typedef void (function_t)(int);
class dummy {
public:
function_t* member;
dummy(function_t* func)
: member{func}{}
};
void f(int) { }
function_t& make_function() { return f; }
dummy d1(f); // OK
dummy d2(make_function()); // OK
dummy d3([](int){}); // OK
Final remarks:
Again, declaring const function_t* member and dummy(const function_t* func) doesn't buy you anything because, as per references, the const qualifiers are ignored.
The initialization of d1 and d2 work because functions are implicitly converted to pointer to functions (see C++ 4.3/1).
If a function argument is of function type, the compiler changes its type to a pointer to function. Hence, dummy's constructor can be declared as dummy(function_t func);.
The initialization of d3 works because captureless lambdas are implicitly converted to pointer to functions. It wouldn't work for lambdas with captures.
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.
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();
If I make a pointer-to-base-member, I can convert it to a pointer-to-derived-member usually, but not when used within a template like Buzz below, where the first template argument influences the second one. Am I fighting compiler bugs or does the standard really mandate this not work?
struct Foo
{
int x;
};
struct Bar : public Foo
{
};
template<class T, int T::* z>
struct Buzz
{
};
static int Bar::* const workaround = &Foo::x;
int main()
{
// This works. Downcasting of pointer to members in general is fine.
int Bar::* y = &Foo::x;
// But this doesn't, at least in G++ 4.2 or Sun C++ 5.9. Why not?
// Error: could not convert template argument '&Foo::x' to 'int Bar::*'
Buzz<Bar, &Foo::x> test;
// Sun C++ 5.9 accepts this but G++ doesn't because '&' can't appear in
// a constant expression
Buzz<Bar, static_cast<int Bar::*>(&Foo::x)> test;
// Sun C++ 5.9 accepts this as well, but G++ complains "workaround cannot
// appear in a constant expression"
Buzz<Bar, workaround> test;
return 0;
}
It simply isn't allowed. According to §14.3.2/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 integral or enumeration type, integral promotions (4.5) and integral conversions (4.7) are applied.
— 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.
— For a non-type template-parameter of type reference to object, no conversions apply. The type referred to by the reference may be more cv-qualified than the (otherwise identical) type of the template argument. The template-parameter is bound directly to the template-argument, which must be an lvalue.
— For a non-type template-parameter of type pointer to function, only the function-to-pointer conversion (4.3) is applied. If the template-argument represents a set of overloaded functions (or a pointer to such), the matching function is selected from the set (13.4).
— For a non-type template-parameter of type reference to function, no conversions apply. If the template-argument represents a set of overloaded functions, the matching function is selected from the set (13.4).
— For a non-type template-parameter of type pointer to member function, 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).
— For a non-type template-parameter of type pointer to data member, qualification conversions (4.4) are applied.
I've emphasized the conversion regarding pointer to data members. Note that your conversion (§4.11/2) is not listed. In C++0x, it remains the same in this regard.