Why can't my code compile? (ideal forwarding and parameter packages)

Here's 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 can't figure out why one call works and one call fails.

EDIT: Changing the parameter package to Args...

from Args&&...

solves the compiler issue. But I would still like to know why this fails.

+3


source to share


1 answer


When a template parameter (or parameter package) is used in two inferred contexts, the inference is performed independently for each and the result must match. It does things like

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

      

difficult to use at best because it Args

is inferred from both the signature of the member function and the subsequent package of arguments; coupled with special rules for link forwarding, you will usually not get an exact match.

Now, in this case, you should get an exact match because this

is a type value A*

, so it Args

will be inferred as an unreferenced type A*

according to the forwarding rule's reference, and that matches the signature B::fn

. Unfortunately, due to bug 56701 , GCC 4.8 thinks it this

has a type A* const

and outputs Args

accordingly (and MSVC apparently has the same error), causing the inconsistency.

I recommend doing the func

type of the template parameter, completely omitting the double inference problem.

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

      



Alternatively, you can constrain func

to a "pointer to a member of B

some type":

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

      

Both have the advantage of properly handling member function pointers over the const

original.

If you really want to restrict the type func

further, use two packaging:

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

      

Another possible workaround, just for this case, involves playing with this

to try and 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 falls into the area of ​​line noise ...

+4


source







All Articles