I searched but really couldn't find an answer why SFINAE happens only when the argument is passed by lvalue ref, but the build succeeds when the arg is passed by rvalue ref:
template <typename T>
class A {
public:
using member_type = T;
};
template <typename AType>
typename AType::member_type f(AType&& m) {
typename AType::member_type res{};
return res;
}
void demo() {
A<int> a;
// ERROR: candidate template ignored: substitution failure
// [with AType = A<int> &]: type 'A<int> &'
// cannot be used prior to '::' because it has no members
f(a);
// BUILD SUCCESS
f(std::move(a));
}
CodePudding user response:
When you have
template <typename AType>
typename AType::member_type f(AType&& m)
You have what is called a forwarding reference. Even though it looks like an rvalue reference, this reference type can bind to lvalues or rvalues. The way it works is when you pass an lvalue to f
, AType
gets deduced to being T&
, and when you pass an rvalue AType
gets deduced to just T
.
So, when you do f(a);
AType
gets deduced as A<int>&
, and you try to form the return type of A<int>&::member_type
which is invalid as references do not have type members.
Conversely when you do f(std::move(a));
, AType
gets deduced to A<int>
and A<int>
does have a member_type
type member.
To fix this you can remove the reference-ness of type by using std::decay_t
like
template <typename AType>
auto f(AType&& m) {
typename std::decay_t<AType>::member_type res{};
return res;
}