Search code examples
c++c++11perfect-forwardingvariadic-functions

Why is my code failing to compile? (perfect forwarding and parameter packs)


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.


Solution

  • 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

    template<typename ...Args>
    void foo(B* b, void (B::*func)(Args...), Args&&... args) { /* ... */ }
    

    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 type A*, so Args will be deduced as the non-reference type A* according to the forwarding reference rules, and that happens to match the signature of B::fn. Unfortunately, due to bug 56701, GCC 4.8 considers this to have type A* const and deduces Args 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.

    template<typename PMF, typename ...Args>
    void foo(B* b, PMF func, Args&&... args) { /* ... */ }
    

    Alternatively, you can constrain func to "pointer to member of B of some sort":

    template<typename F, typename ...Args>
    void foo(B* b, F B::* func, Args&&... args) { /* ... */ }
    

    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:

    template<typename ...Args, typename... FArgs>
    void foo(B* b, void (B::*func)(FArgs...), Args&&... args) 
    

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