简体   繁体   中英

Template argument deduction in alias templates - typedefing any member function pointer

While answering a question, I proposed utilizing template aliases for typedefing the signature of a member function; that is, not just typedefing a member function but being able to factor out the target class that contains the method:

template<typename T>
using memberf_pointer = int (T::*)(int, int); 

Though this seems to cover what the question asked, I tried to generalize it for arbitrary function arguments:

template<typename T, typename... Args>
using memberf_pointer = int (T::*)(Args&&...); 

It fails with argument deduction issues (basically it assumes an empty arument list). Here's a demo :

#include <iostream>

class foo
{
public:
  int g (int x, int y) { return x + y ; }
};

template<typename T, typename...Args>
using memberf_pointer = int (T::*)(Args&&...); 

int main()
{
  foo f ;
  memberf_pointer<foo> mp = &foo::g ;
  std::cout << (f.*mp) (5, 8) << std::endl ;
}

Why is this? Is there a way to get it to work?

Why not simply use an auto in this case? The only advantage of having a template in this case is being able to provide your types explicitly.

On the other hand, if you want your template to automatically deduce a function type, it needs to be directly parametrized on that type. If you also want it to provide all of the building blocks for you, the simplest way is to specialize it for functions or member functions. Example:

template<typename T> struct memberf_pointer_descriptor;

template<typename TOwner, typename TRet, typename... Args>
struct memberf_pointer_descriptor<TRet(TOwner::*)(Args...)>
{
    // Your stuff goes here.
    using type = TRet(TOwner::*)(Args...);
};

memberf_pointer_descriptor<decltype(&foo::g)>;

Or a function template that directly takes foo::g as an argument, to mitigate the need of using an explicit decltype. Depends on your needs.

A way to make your example work is the following:

#include <iostream>

class foo
{
public:
    int g(int x, int y) { return x + y; }
};

template<typename T, typename...Args>
using memberf_pointer = int (T::*)(Args...);

int main()
{
    foo f;
    memberf_pointer<foo, int, int> mp = &foo::g;
    std::cout << (f.*mp) (5, 8) << std::endl;
}

It removes the reference on the variadic template parameter and when instantiating the memberf_pointer it supplies also the member function parameters. But auto is probably the way to go...

C++ doesn't feature something like a Hindley-Milner type deduction system and it won't work for your specific rvalue assignment

memberf_pointer<foo> mp = &foo::g ;

As for some quick workarounds you could

  1. Just drop the whole struct and use auto

     auto mp = &foo::g; 
  2. Explicitly provide the types or the pointer type

     template<typename T> using memberf_pointer = T; memberf_pointer<decltype(&foo::g)> mp = &foo::g; 

Cfr. Template argument deduction

The wording in both the title and body of the question is very misleading. There is zero template deduction going on in your example, anywhere. When you write:

memberf_pointer<foo> mp = &foo::g;

memberf_pointer<foo> is an alias template, yes, but it's a specific instantiation of one. There is no deduction going on because you are providing the exact type of mp . That line is exactly equivalent to:

int (foo:*mp)() = &foo::g;

which doesn't compile for the obvious reason that g takes arguments. The way to get template deduction in an assignment statement is to use auto :

auto mp = &foo::g;

The type of mp will be the same type as U had you called:

template <typename U> void meow(U );
meow(&foo::g);

which is to say, int (foo::*)(int, int) .

Similarly, you could do:

decltype(&foo::g) mp = &foo::g;

Which would give you the same type as before.

Of course, even if you provided the correct argument list:

memberf_pointer<foo, int, int> mp = &foo::g;

That still wouldn't compile since your alias adds rvalue references to both arguments. The type of mp there is int (foo::*)(int&&, int&&) , which would not match &foo::g . Perhaps you'd intended this to be deduction as if by forwarding-reference, but that is not the case here. In order to use the alias correctly you'd have to rewrite it:

template<typename T, typename...Args>
using memberf_pointer = int (T::*)(Args...); 

memberf_pointer<foo, int, int> mp = &foo::g;

Had we had a member function that took an rvalue reference, we could then explicitly provide it:

class bar
{
public:
  int h(int&& x, int&& y) { return x + y ; }
};

memberf_pointer<bar, int&&, int&&> mb = &bar::h;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM