John
John

Reputation: 161

Why template argument cannot be deduced in this context?

Could anyone explain why compilers (g++, visual c++) fail to deduce the template argument in this case?

struct MyClass
{
    void Foo(int x)&  {}
    void Foo(int x)&& {}
};

template<typename T>
void CallFoo(void(T::*func)(int)&)
{
    //create instance and call func
}

int main()
{
   CallFoo(&MyClass::Foo); // Fails to deduce T
}

Why compilers cannot deduce T to MyClass? This happens only for methods overloaded by ref qualifiers. If a method is overloaded by const-ness or parameter types, everything works fine. It seems that only Clang can deduce T in this case.

Upvotes: 16

Views: 737

Answers (2)

CJ13
CJ13

Reputation: 111

If you want your template to bind to different reference types, you need to use the universal reference

template<typename T>
void func(T&& arg)
{
    other_func(std::forward<T>(arg));
}

That will bind to either lvalue or rvalue references. std::forward will make sure the appropriate reference is used in subsequent calls. I'm not sure how to fit the double ampersand into your code, but maybe just

template<typename T>
void CallFoo(void(T::*func)(int)&&)

Perhaps better would be

template<typename func_t>
void CallFoo(func_t && f)
{
    call(std::forward<func_t>(f));
}

template<typename func_t>
void call(typename std::remove_reference<func_t> & f)
{
    f();
}

template<typename func_t>
void call(typename std::remove_reference<func_t> && f)
{
    f();
}

or whatever syntax you need to invoke a function pointer, maybe *f();

And if you want to pass arguments as well:

template<typename func_t, typename ... args_t>
void CallFoo(func_t && f, args_t && ... args)
{
    call(std::forward<func_t>(f), std::forward<args_t>(args)...);
}

template<typename func_t, typename ... args_t>
void call(typename std::remove_reference<func_t> & f, args_t && ... args)
{
    f(std::forward<args_t>(args)...);
}

template<typename func_t, typename ... args_t>
void call(typename std::remove_reference<func_t> && f, args_t && ... args)
{
    f(std::forward<args_t>(args)...);
}

Upvotes: 0

AMA
AMA

Reputation: 4214

Summarizing discussion in the comments: the support for reference-qualified member functions as template arguments is a relatively new feature for some compilers. However, the latest versions of most compilers will compile such code.


For example:

#include <iostream>

struct MyClass
{
    void Foo(int) const &
    {
        std::cout << "calling: void Foo(int) const &\n";
    }
    void Foo(int) const &&
    {
        std::cout << "calling: void Foo(int) const &&\n";
    }
};

template<typename T>
void CallFoo_lvalue(void (T::*foo)(int) const &)
{
    T temp;
    (temp.*foo)(0);
}

template<typename T>
void CallFoo_rvalue(void (T::*foo)(int) const &&)
{
    (T{}.*foo)(0);
}

int main()
{
   CallFoo_lvalue(&MyClass::Foo);
   CallFoo_rvalue(&MyClass::Foo);
}

Will compile with:

producing the following output:

calling: void Foo(int) const &
calling: void Foo(int) const &&

For those who are wondering what & and && are for: here's the quote from @JustinTime:

Basically, & is the lvalue ref-qualifier, and && is the rvalue ref-qualifier (binds to temporary object); in his example, MyClass m; m.Foo(3); would call the top one, while MyClass{}.Foo(3); would call the bottom one. They act on the implicit object parameter; lvalue ref-qualifier binds to lvalue reference, and rvalue ref-qualifier binds to rvalue reference (functions that have neither take the parameter as lvalue reference, but let it bind to either). Note that they don't actually change *this's type.

Upvotes: 1

Related Questions