2

I am trying to replicate the (I guess) typical SFINAE example to tell whether a type has a certain method. My code is basically the one found in the accepted answer of a previous popular question on the topic:

#include <iostream>
#include <vector>
#include <set>
#include <map>
#include <list>

template <typename T>
class has_push_back_sfinae {
    typedef int8_t yes;
    typedef int16_t no;

    template <typename C> static constexpr yes test(decltype(&C::push_back));
    template <typename C> static constexpr no test(...);    

 public:
    static constexpr bool value = (sizeof(test<T>(0)) == sizeof(yes));
};

template <typename T>
constexpr bool has_push_back = has_push_back_sfinae<T>::value;

int main() {
    std::cout << std::boolalpha;
    std::cout << has_push_back<int> << std::endl;
    std::cout << has_push_back<std::set<int>> << std::endl;
    std::cout << has_push_back<std::map<int, char>> << std::endl;
    std::cout << has_push_back<std::vector<char>> << std::endl;
    std::cout << has_push_back<std::list<int>> << std::endl;
    std::cout << has_push_back<std::string> << std::endl;
    return 0;
}

Basically, has_push_back<T> is meant to be true if and only if push_back is a method of T. I expected that my output had 3 false lines, followed by 3 true ones. However, this is the actual output:

false
false
false
false
false
true

Am I missing something?

(PS: Is there any more standard or nicer way to write such a class?)

Victor Martin
  • 79
  • 1
  • 8
  • 1
    by adding `std::vector::push_back;` the compiler report *error: statement cannot resolve address of overloaded function* ([wandbox](https://wandbox.org/permlink/xU8uegNusdna4gHd)) – apple apple Mar 06 '21 at 19:05
  • `push_back` is overloaded for `list` and `vector`. Which version do you want to select by `&C::push_back` ? – rafix07 Mar 06 '21 at 19:07

2 Answers2

6

This method only works for non-overloaded functions. If it's overloaded, you can't make a pointer-to-member to it, unless you cast it to a specific pointer type right away.

Such casting is error-prone, so I instead recommend to try to call the function:

/*...*/ constexpr yes test(decltype(void(
    std::declval<C &>().push_back(std::declval<const typename C::value_type &>())), int{}));

IMO, the approach to SFINAE suggested in the linked answer is unnecessarily verbose. Here's what I would do:

template <typename T, typename = void> struct has_push_back_sfinae : std::false_type {};
template <typename T> struct has_push_back_sfinae<T,
    decltype(void(std::declval<T &>().push_back(std::declval<const typename T::value_type &>())))
> : std::true_type {};
template <typename T> inline constexpr bool has_push_back = has_push_back_sfinae<T>::value;
HolyBlackCat
  • 45,832
  • 5
  • 81
  • 134
3

HolyBlackCat has already written a great answer explaining why the attempted approach didn't work and what more standard C++17 SFINAE should look like. I just wanted to add my two cents by providing an additional approach available in C++20.

Is there any more standard or nicer way to write such a class?

By defining a concept, it's very straight-forward to constrain your types to those where the member method push_back() is well-formed:

template <typename T>
concept has_push_back = requires (T& t, const T::value_type& v) {
    { t.push_back(v) };
};

static_assert(not has_push_back<int>);
static_assert(not has_push_back<std::set<int>>);
static_assert(not has_push_back<std::map<int, char>>);
static_assert(has_push_back<std::vector<char>>);
static_assert(has_push_back<std::list<int>>);
static_assert(has_push_back<std::string>);

Try it on godbolt.org: Demo.

Patrick Roberts
  • 40,065
  • 5
  • 74
  • 116
  • Thanks! I've been trying to start using C++20 and this new approach seems amazing! As a small comment, I think you can now drop the `typename` to make it even simpler ;) – Victor Martin Mar 07 '21 at 21:09
  • @VictorMartin interesting... thanks for the tip. I'm still a little fuzzy on when `typename` is necessary. I thought it was always necessary whenever there's a `::` in the identifier, but seems that assumption is now broken. – Patrick Roberts Mar 07 '21 at 21:11