Reputation: 1629
template <typename T>
struct has_xxx {
private:
using true_type = char;
using false_type = long;
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
public:
enum { value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type) }; // comment3
};
struct Foo { int xxx() {return 0;}; };
struct Foo2 {};
int main() {
static_assert(has_xxx<Foo>::value, "");
}
This is a struct to detect whether a struct has a certain method. I have some questions about the code.
Upvotes: 2
Views: 468
Reputation: 41220
In comment 1, what does this '&C' mean, why can't I just write 'C::xxx'
The &
is really referring to the xxx
portion of &C::xxx
. That is, get the address of the member xxx
within C
(which is hopefully a function, and not a static member --- more on that later)
In comment 2, what does the parameter '...' mean, is it a parameter pack, or it represents any kind of parameter
the ...
, or ellipsis operator is a variadic argument that accepts anything. This is a pretty C-style way of doing things, and usually isn't very typesafe. The place you'll see it most today is in swallowing exceptions:
try{
ThrowableFunctionCall();
} catch(...) // swallow any exceptions
{}
(Note: this is NOT a parameter pack, or part of a fold expression. Refer to §5.2.2/6)
In comment 3, how does has_xxx_impl(0) work? T is replaced by Foo, then how about the parameter 0? why the first function is selected?
sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
is a sort-of clever way of using overload resolution to set the value to true_type
or false_type
. Allow me to explain:
calling has_xxx_impl<T>
at first makes it appear that both of
template <typename C>
static true_type has_xxx_impl(decltype(&C::xxx)); // comment 1
template <typename C>
static false_type has_xxx_impl(...); // comment 2
Are viable candidates, because they have the same name, and could maybe both be instantiated with type T
. However
decltype(&C::xxx)
Is attempting to create a pointer to member type (e.g. int(C::*)()
for member function pointer) and it's doing so in a type-deduced context. If the statement is ill-formed, then SFINAE takes effect and makes it a bad candidate, leaving us with only
template <typename C>
static false_type has_xxx_impl(...); // comment 2
and since ...
matches anything, the value is set to false_type
or long
.
If the statement is well-formed, then 0
can be properly assigned to a function pointer type (it is begrudgingly converted to a pointer type with a null
value). This conversion is preferred over matching ...
(try it yourself!), and hence the value takes on true_type
which is a char
.
Finally,
value = sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
we can say is either comparing size of char
to size of long
(doesn't have a xxx
case)
or a size of long
to a size of long
(has a xxx
case).
Note that all of this is completely unnecessary in C++11/14, and indeed fails if xxx
is a member variable and not a function:
struct Foo3{static const int xxx;};
The comment by melak47, while simpler, has a similar downfall.
A better solution would be to use the is_member_function_pointer
type trait:
template<typename...> // parameter pack here
using void_t = void;
template<typename T, typename = void>
struct has_xxx : std::false_type {};
template<typename T>
struct has_xxx<T, void_t<decltype(&T::xxx)>> :
std::is_member_function_pointer<decltype(&T::xxx)>{};
Upvotes: 1
Reputation: 7834
Following explanation is imperfect and doesn't quote standard. I tried to explain it as understandable as possible.
has_xxx_impl(decltype(&C::xxx))
accepts type which is produce by decltype(&C::xxx)
&C::xxx
is a pointer to member and decltype(&C::xxx)
is its type.
the other one has_xxx_impl(...)
accepts "anything"
so, when you have has_xxx_impl<T>
compiler must choose, one of above. It prefers the better match. So when type contains xxx
the first one is better match than the ...
version. If it doesn't contain such member, the function cannot be chosen and it has to use the other one (...
)
Thanks to SFINAE
- the substitution failure (when trying to pass type of non-existent member) doesn't produce an error (other overload is chosen)
Notice, that both functions returns different types (true_type
and false_type
which are defined to have distinct sizes) so with sizeof(has_xxx_impl<T>(0)) == sizeof(true_type)
you can tell, which one was chosen. The result of this comparison is set as a value
which you can access from the outside.
Upvotes: 1