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

1

25

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.

Cellist answered 9/9, 2020 at 19:20 Comment(1)
It has to do with the fact that std::vector's begin() is overloaded (const-overloaded to be specific).Spectacled
T
33

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;
}
Ternary answered 9/9, 2020 at 19:33 Comment(7)
@Cellist I suggest posting a new answer (if nobody showed those approaches yet). Or at least amending the original answer instead of completely replacing the code.Ternary
@Cellist the answer you quote is from before C++11 and targeting pre C++11 the answer is fine as is. Second answer on same question is already more up to dateExplanatory
why is the void( ... ) needed? in the "shorter" solutionExplanatory
@idclev463035818 It is to remove the ambiguity of the returned value of the method. You can create void statement like this and give it any parameters. Since this always creates a noop then any parameter becomes valid and thus the ambiguity is nullified since it doesn't matter which version is taken. To sum it up: cast into a callable type -> call the method -> pass it into a parameter to a void statement, that effectively is void f(...){} and then transform this into a type to put inside the template argument; if this fail the other definition is used as per SFINAE.Cellist
@Ternary I have a suggestion. It should be indicated that the inside decltype(void(...)) it is a full function call sample that has to be present (e.g. .resize(0))Cellist
@idclev463035818 Well, the resulting type must be void, to match the default argument. I could've done std::void_t<decltype(blahblah.begin())> with the same effect, but Clang sometimes does weird things with it with it, so I avoid it.Ternary
Your version is also better because it is unspecified what happens (including making your program ill-formed) if you take the address of a standard library function: eel.is/c++draft/constraints#namespace.std-6Howlyn

© 2022 - 2024 — McMap. All rights reserved.