I was wondering if it was possible to create a class that would serve as a combination between std::enable_if and a SFINAE member detector.
class foo
{
public:
int bar;
};
template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
return value.bar;
}
So I attempted to do this.
class foo
{
public:
int bar;
};
template <class C, C>
class Check;
template <class T, class Enable = void>
class enable_if_has_bar
{};
template <class T>
class enable_if_has_bar<T, Check <decltype(&T::bar),&T::bar>>
{
public:
typedef decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0)) type;
};
template <class T>
typename enable_if_has_bar<T>::type ReturnBar (const T& value)
{
return value.bar;
}
int main ()
{
foo foobar;
foobar.bar = 42;
cout << ReturnBar(foobar) << endl;
}
It doesn’t seem to work, and I’m not quite as versed in the fine art of SFINAE as I could be. Perhaps someone could improve/fix it? Because I’m at a loss.
I generally prefer to create custom
enable_if-style types as you’ve attempted, because I find the code clearer to read with a single trait type, rather than a combination ofenable_if<some_trait<T>, another_trait<T>>. But in this case there are a few problems in your code stopping it working.Your
enable_if_has_barspecialization will never be selected, the return type ofReturnBarjust instantiates the primary template,enable_if_has_bar<foo, void>, and that never defines the nestedtype. Nothing causes the specialization to be instantiated so there is nothing that checks whetherT::baris a valid expression.Your
decltype(static_cast<T*>(0)->*static_cast<decltype(&T::bar)>(0))expression will result inint¬intas you probably want. This is becausedecltype(foobar.*(&foo::bar))is equivalent todecltype(foobar.bar)andfoobar.baris an lvalue, so thedecltypeisint&. The functionReturnBarwould fail to compile if it returnsint&because the parametervalueis const, so you can’t bindvalue.barto a non-constint&.Here’s a working version:
This first declares the helper
has_barto answer the question of whether the type has the nested member. That helper uses SFINAE to get atrueorfalsevalue. If&T::baris a valid expression then the first overload oftestwill be used, which returnstrue_typeand sovaluewill be set totrue_type::valuei.e.true. Otherwise the fallback overload will be selected andvalueset tofalse.Then the
enable_if_has_bartemplate uses a default template parameter which is deduced as the value ofhas_bar<T>::value. The primary template is used whenhas_bar<T>::valueis false. The specialization is used whenhas_bar<T>::valueis true, in which case we know the expression&T::baris valid and can use it in a decltype expression to get the type.std::decayis used to turn theint&result of the decltype expression to justint. Inheriting fromdecayis a bit shorter than using it to define the member, which would be:I used the standard utility
declval<T>()in the unevaluated expressions, which is a bit shorter to type and a bit more idiomatic and expressive thanstatic_cast<T*>(0).An alternative to using
decaywould be another helper type to get the typeintfrom the typeint T::*e.g.(The name
remove_classisn’t very good, but basically it takes a pointer-to-data-member type and gives the type of the member.)