The code below correctly checks if the type T
has a method sort
. But when I modify the line marked (*)
by changing decltype(&U::sort,...)
to decltype(U::sort,...)
(the symbol &
is removed), then the code returns always false
.
Why?
Why the name itself is not enough? What does this &
mean?
#include <iostream>
#include <type_traits>
template <typename T>
class has_sort {
template <typename U>
static auto check(bool) -> decltype(&U::sort, std::true_type()); // (*)
template <typename U>
static std::false_type check(...);
public:
using type = decltype(check<T>(true));
static bool const value = type::value;
};
int main() {
struct Foo { void sort(); };
struct Foo2 { void sort2(); };
std::cout << "Foo: " << has_sort<Foo>::value << std::endl;
std::cout << "Foo2: " << has_sort<Foo2>::value << std::endl;
std::cout << "int: " << has_sort<int>::value << std::endl;
}
The answer is simple: you can't take the address of a member function without the &
. You can't try for yourself:
auto fun = Foo::sort; // error
The standard require that member function pointer must be used with the &
, because the syntax would be ambiguous without it. Imagine that in a template:
template<typename T>
void test() {
T::test2; // is it a member function pointer or static data member?
}
So the sfinae check is right: without the &
, the check will be true if the type T
would have a static data member named sort
.
However, you could bypass this limitation with this trick, although, it is for demonstration purposes and I would not advice you to do this:
struct Foo {
void sortImpl();
static constexpr auto sort = &Foo::sortImpl;
};
Then the check for a static data member named sort
would be right, and sort
would be a function pointer.
By using &U::foo
you are generically checking if the type U
contains either a member method (static or not) or a data member (static or not).
Because of that, it will match all the following types (and others, if you consider also the specifiers):
struct Foo { void sort(); };
struct Foo { static void sort(); };
struct Foo { int sort; };
struct Foo { static int sort; };
On the other side, U::foo
cannot be used to detect member methods (even if you can still use that to detect data members in some cases).
Anyway, for you have also template <typename U> static std::false_type check(...);
at your disposal, when you try to detect the sort
member method, the error during the specialization of the function above is silently discarded because of sfinae rules and this one is picked up.
If you want to be more strict and require sort
to be a function (either static or not), you should include the utility
header and use std:: declval
instead. This way, the ampersand is no longer required:
template <typename U>
static auto check(bool) -> decltype(std::declval<U>().sort(), std::true_type()); // (*)
This way, data members names sort
won't be detected anymore.
If I can give you a suggestion, you can simplify a bit things up by using int
/char
overloading and constexpr
functions.
As an example:
template <typename T>
class has_sort {
template <typename U>
constexpr static auto check(int) -> decltype(std::declval<U>().sort(), std::true_type()) { return {}; }
template <typename U>
constexpr static std::false_type check(char) { return {}; }
public:
static constexpr bool value = check<T>(0);
};
If you can use C++14, template variables are even more compact:
template<typename T, typename = void>
constexpr bool has_sort = false;
template<typename T>
constexpr bool has_sort<T, decltype(std::declval<T>().sort(), void())> = true;
You can use it in your example as it follows:
std::cout << "Foo: " << has_sort<Foo> << std::endl;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With