Почему аргумент шаблона не может быть выведен в этом контексте?

Может ли кто-нибудь объяснить, почему компиляторы (g ++, visual C++) не могут вывести аргумент шаблона в этом случае?

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
}

Почему компиляторы не могут вывести T в MyClass? Это происходит только для методов, перегруженных квалификаторами ref. Если метод перегружен константностью или типами параметров, все работает нормально. Кажется, что только Clang может вывести T в этом случае.

2 ответа

Подводя итоги обсуждения в комментариях: поддержка ссылочных функций-членов в качестве аргументов шаблона является относительно новой функцией для некоторых компиляторов. Однако последние версии большинства компиляторов будут компилировать такой код.


Например:

#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);
}

Скомпилируем с:

  • gcc (работает с 7.0.0)
  • Visual C++ (работает с v19.10.24903.0)

производя следующий вывод:

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

Для тех, кому интересно, что & а также && для: вот цитата из @JustinTime:

По сути, & - это ref-квалификатор lvalue, а && - это ref-квалификатор rvalue (привязывается к временному объекту); в его примере MyClass m; m.Foo(3); вызовет верхний, а MyClass{}.Foo(3); назвал бы нижний. Они действуют на неявный параметр объекта; lvalue ref-qualifier связывается со ссылкой lvalue, а rvalue ref-qualifier связывается со ссылкой rvalue (функции, которые не принимают параметр в качестве ссылки lvalue, но разрешают его привязку к любому из них). Обратите внимание, что они на самом деле не меняют * этот тип.

Если вы хотите, чтобы ваш шаблон связывался с различными ссылочными типами, вам нужно использовать универсальную ссылку

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

Это будет привязывать либо к ссылкам lvalue, либо к rvalue. std::forward обеспечит использование соответствующей ссылки в последующих вызовах. Я не уверен, как вписать двойной амперсанд в ваш код, но, возможно, просто

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

Возможно, лучше будет

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();
}

или любой другой синтаксис, который вам нужен для вызова указателя на функцию, может быть, *f();

И если вы также хотите передать аргументы:

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)...);
}
Другие вопросы по тегам