Here is the program:
#include <memory>
struct A;
struct B {
void fn(A* ptr) {}
};
template<typename ...Args>
void foo(B* b, void (B::*func)(Args...), Args&&... args)
{
(b->*func)(std::forward<Args>(args)...);
}
struct A {
void bar() { B b; foo(&b, &B::fn, this); } // fails
};
int main()
{
A a;
B b;
foo(&b, &B::fn, &a); // passes
return 0;
}
Here is the compiler output:
foo.cpp: In member function 'void A::bar()':
foo.cpp:16:47: error: no matching function for call to 'foo(B*, void (B::*)(A*), A* const)'
void bar() { B b; foo(&b, &B::fn, this); } // fails
^
foo.cpp:16:47: note: candidate is:
foo.cpp:10:10: note: template<class ... Args> void foo(B*, void (B::*)(Args ...), Args&& ...)
void foo(B* b, void (B::*func)(Args...), Args&&... args)
^
foo.cpp:10:10: note: template argument deduction/substitution failed:
foo.cpp:16:47: note: inconsistent parameter pack deduction with 'A*' and 'A* const'
void bar() { B b; foo(&b, &B::fn, this); } // fails
I cannot figure out why one call works and one call fails.
EDIT : Changing the parameter pack to Args...
from Args&&...
solves the compiler problem. But, I would still like to know why it fails.
When a template parameter (or parameter pack) is used in two deduced contexts, deduction is done independently for each and the result must match. This makes things like
tricky to use at best, because
Args
is being deduced from both the member function signature and the subsequent pack of arguments; coupled with the special rules for forwarding references, you'll usually not be getting an exact match.Now, in this case, you should get an exact match, because
this
is a prvalue of typeA*
, soArgs
will be deduced as the non-reference typeA*
according to the forwarding reference rules, and that happens to match the signature ofB::fn
. Unfortunately, due to bug 56701, GCC 4.8 considersthis
to have typeA* const
and deducesArgs
accordingly (and MSVC apparently has the same bug), causing a mismatch.I recommend making
func
's type a template parameter, side-stepping the double-deduction issue entirely.Alternatively, you can constrain
func
to "pointer to member ofB
of some sort":Both also have the benefit of handling pointer to
const
member functions properly, compared to the original.If you really want to constrain
func
's type further, use two packs:Another possible workaround, for this case only, involves playing with
this
to try to remove the erroneous const-qualification;+this
works with GCC 4.8 but not MSVC;&*this
works with MSVC but not GCC 4.8;+&*this
seems to work with both but is getting into line-noise territory...