Почему использование SFINAE для определения существования метода не работает с std::vector::begin

Я ищу способ определить, есть ли у класса шаблона методы begin, end и resize.

Я попробовал модифицированную версию этого ответа:

#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;
}

Однако это печатает 0. Что забавно, так это то, что это будет работать с cbegin и cend но не с begin, end и resize. Однако определенные пользователем классы, реализующие эти методы, работают нормально.

Я пробовал это как с g++, так и с Visual Studio 19, и я получаю те же результаты, поэтому похоже, что это не связано с компилятором или реализацией STL.

1 ответ

Решение

std::vector имеет переизбыток begin: одна перегрузка const а другой нет.

Компилятор не знает, какую перегрузку использовать, когда вы пишете &C::begin, поэтому он рассматривает эту неоднозначность как ошибку, которую обнаруживает SFINAE.

Вместо того, чтобы формировать указатель на beginпросто назовите это:

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

(Если это не очевидно, если вы пытаетесь обнаружить функцию с параметрами, вы должны предоставить аргументы, например .resize(0) (или возможно .resize(std::size_t{})) вместо просто .resize().)

А вот еще одно, более короткое решение:

#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;
}

А вот и модный C++20 requires-основное решение:

#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;
}
Другие вопросы по тегам