Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE with ampersand before a function vs its name

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;
}
like image 998
olpa Avatar asked Oct 01 '16 21:10

olpa


2 Answers

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.

like image 162
Guillaume Racicot Avatar answered Nov 13 '22 14:11

Guillaume Racicot


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;
like image 28
skypjack Avatar answered Nov 13 '22 14:11

skypjack