16

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.

John
  • 161
  • 2
  • well, [clang accepts it happily.](http://coliru.stacked-crooked.com/a/f34a8c6c3719fb17) Perhaps the issues touched on [here](http://stackoverflow.com/questions/18746237/ref-qualified-member-functions-as-template-arguments?rq=1) still aren't resolved for gcc or vc++? – jaggedSpire Jan 05 '17 at 18:41
  • @jaggedSpire The more I look at it the more confused I get. I'm not sure if it actually contributes to the function type or just describes that the type of `*this` is. – NathanOliver Jan 05 '17 at 18:47
  • 2
    @NathanOliver [dcl.fct]/6: "The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq ... are part of the function type." and [clang treats it as part of the type](http://coliru.stacked-crooked.com/a/db473618921ab41a) – jaggedSpire Jan 05 '17 at 18:57
  • @jaggedSpire Thanks. Look like a gcc bug then. – NathanOliver Jan 05 '17 at 19:04
  • I can't find a listed bug in the gcc bug tracker that matches this situation, but I'm no expert on searching their tracker... – jaggedSpire Jan 05 '17 at 19:15
  • 2
    g++7 seems to be able to compile this. – Holt Jan 05 '17 at 19:17
  • 2
    @NathanOliver 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](http://ideone.com/iF3SnV). 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. – Justin Time - Reinstate Monica Jan 05 '17 at 22:11
  • They don't seem to be all that well-known, though, since there's usually not much need to overload member functions based on whether or not they're called on temporaries. [They're built into the type system, and can be combined with cv-qualifiers](http://rextester.com/ZKKFM60237). (Used MSVC for an example, since its `type_info::name()` automatically demangles the name, making it easier to see the difference.) – Justin Time - Reinstate Monica Jan 05 '17 at 22:17
  • Is is just me or should the call to the function actually be CallFoo(&MyClass::Foo)? –  Feb 16 '19 at 00:42

2 Answers2

1

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.

SirGuy
  • 10,660
  • 2
  • 36
  • 66
AMA
  • 4,114
  • 18
  • 32
0

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)...);
}
CJ13
  • 111
  • 1
  • 4