Почему аргумент шаблона не может быть выведен в этом контексте?
Может ли кто-нибудь объяснить, почему компиляторы (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)...);
}