Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why using SFINAE to find if a method exists fails with std::vector::begin

Tags:

c++

sfinae

I'm looking for a way to detect if a template class has the methods begin, end and resize.

I tried a modified version of this answer:

#include <iostream>
#include <vector>

// SFINAE test
template <typename T>
class has_method
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( decltype(&C::begin) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_method<std::vector<int>>::value << std::endl;
    
    return 0;
}

However this prints 0. What is funny is that this will work with cbegin and cend but not with begin, end and resize. User defined classes implementing those methods works fine though.

I've tried this with both g++ and with Visual Studio 19 and I get the same results so this doesn't seem to be related to the compiler or the STL's implementation.

like image 768
TommyD Avatar asked Sep 09 '20 19:09

TommyD


1 Answers

std::vector has an oveloaded begin: one overload is const and the other one is not.

The compiler doesn't know which overload to use when you write &C::begin, so it treats this ambiguity as an error, which gets detected by SFINAE.

Instead of forming a pointer to begin, just call it:

// ...
template <typename C> static one test( decltype(void(std::declval<C &>().begin())) * );
// ...

(In case it's not obvious, if you're trying to detect a function with parameters, you must provide arguments, e.g. .resize(0) (or perhaps .resize(std::size_t{})) instead of just .resize().)

And here's another, shorter solution:

#include <iostream>
#include <vector>

template <typename T, typename = void>
struct has_begin : std::false_type {};

template <typename T>
struct has_begin<T, decltype(void(std::declval<T &>().begin()))> : std::true_type {};

int main(int argc, char *argv[])
{
    std::cout << has_begin<std::vector<int>>::value << std::endl;
}

And here's a fancy C++20 requires-based solution:

#include <iostream>
#include <vector>

template <typename T>
concept has_begin = requires(T t) {t.begin();};

int main(int argc, char *argv[])
{
    std::cout << has_begin<std::vector<int>> << std::endl;
}
like image 73
HolyBlackCat Avatar answered Nov 07 '22 18:11

HolyBlackCat