Reputation: 5344
I have a class template CFoo<T>
. I want to allow implicit casts to other instantiations of CFoo
, but only for those whose template argument is a base class of T
.
I tried to use SFINAE, but neither of my attempts worked on any compiler I tried (VC 2012 or gcc):
#include <type_traits>
template <class T> class CFoo {
public:
template <class Q> operator
// typename std::enable_if<std::is_base_of<Q, T>::value, CFoo<Q>&>::type // SHOULD WORK?
// typename std::enable_if<1, CFoo<Q>&>::type // SHOULD WORK?
CFoo<Q>& // compiles, but doesn't restrict on Q like I want
() const {
return *(CFoo<Q>*)this;
}
};
class A {};
class B : public A {};
int main(int argc, char* argv[])
{
CFoo<B> b;
CFoo<A>& a = b;
return 0;
}
Why don't either of the commented out attempts at SFINAE work here? In both cases I just get an error for an invalid initialization of a
, as if my operator didn't get called.
Upvotes: 3
Views: 1251
Reputation: 303156
According to [temp.deduct.conv]:
Template argument deduction is done by comparing the return type of the conversion function template (call it P) with the type that is required as the result of the conversion (call it A; see 8.5, 13.3.1.5, and 13.3.1.6 for the determination of that type) as described in 14.8.2.5.
In the simple case:
template <class Q>
operator CFoo<Q>& const;
That's straightforward, we try to deduce CFoo<Q>&
against CFoo<A>&
. There's other rules in that section, but ultimately that deduction succeeds with Q == A
.
Both of your other attempts fail for the same reason. I'll pick the simpler one:
template <class Q>
operator typename std::enable_if<1, CFoo<Q>&>::type const;
Here, we're trying to deduce typename std::enable_if<1, CFoo<Q>&>::type
. This is a non-deduced context (it's a nested-name-specifier of a type that was specified using a qualified-id), so deduction fails. So this conversion function won't be considered, so the assignment fails as no conversion is found.
You need the return type to be a deduced context, so the SFINAE has to go here:
template <class Q,
typename = std::enable_if_t<std::is_base_of<Q, T>::value>>
operator CFoo<Q>& const;
That way, we have something to deduce (CFoo<Q>&
) - and the deduction can succeed (if Q
is a base of T
):
CFoo<A>& a = b; // OK
CFoo<int>& i = b; // deduction failure on Q, so there's no viable conversion function
// so this is an error
That said, while I got carried away with solving template puzzles, as T.C. points out, this really isn't a good solution because:
return *(CFoo<Q>*)this;
just does a reinterpret_cast
(and a const_cast
) so it really cannot possibly be doing anything reasonable and you will almost certainly (unless CFoo
is trivial) end up with undefined behavior by trying to access its members with the wrong type.
You probably instead want to add a conversion constructor rather than a conversion function:
template <typename Q,
typename = std::enable_if_t<std::is_base_of<T, Q>::value>>
CFoo(CFoo<Q> const& ) { }
That way, when you do:
CFoo<A> a = b; // a is not a reference anymore
You are constructing a new object that will necessarily be valid.
Upvotes: 8