Матчи SFINAE не go, как ожидалось - PullRequest
1 голос
/ 31 марта 2020

Я использую и g ++ 7.5.0, и clang 6.0.0 в ubuntu, чтобы попробовать функцию SFINAE автоматического диспетчерского вызова функции в соответствии с существованием метода объекта, и результат не go, как ожидалось.

Я ожидал, что для контейнера vector он должен вызывать метод clear вектора в функции уничтожения контейнера. для примитивных типов, таких как int, он не делает ничего, кроме распечатки сообщений. но они дают оба позже сейчас. Интересно, что здесь не так.

#include <iostream>
#include <typeinfo>
#include <vector>

using namespace std;

template <typename T> struct has_clear {
    typedef char true_type;
    typedef int false_type;

    template <typename U, size_t (U::*)() const> struct SFINAE {
    };
    template <typename U> static char Test(SFINAE<U, &U::clear> *);
    template <typename U> static int Test(...);

    static const bool has_method = sizeof(Test<T>(nullptr) == sizeof(char));
    typedef decltype(Test<T>(nullptr)) ret_type;
    // typedef Test<T>(0) type_t;
};

template <typename T> class MyContainer {
    // using typename has_clear<T>::true_type;
    // using typename has_clear<T>::false_type;
    T _obj;

  public:
    MyContainer(const T &obj) : _obj(obj) {}
    // static void clear(MyContainer *m);
    void clear(const typename has_clear<T>::true_type t)
    {
        cout << "the " << typeid(_obj).name() << " object has clear() function!" << endl;
        cout << "typeid(t).name(): " << typeid(t).name() << endl;
        _obj.clear();
        cout << "clear has be done!" << endl;
    }
    void clear(const typename has_clear<T>::false_type t)
    {
        cout << "the " << typeid(_obj).name() << " object has no clear() function!" << endl;
        cout << "typeid(t).name(): " << typeid(t).name() << endl;
        cout << "just do nothing and quit!" << endl;
    }
    ~MyContainer()
    {
        cout << "has_clear<T>::true_type: " << typeid(typename has_clear<T>::true_type()).name()
             << endl;
        cout << "has_clear<T>::flase_type: " << typeid(typename has_clear<T>::false_type()).name()
             << endl;
        clear(typename has_clear<T>::ret_type());
    };
    // template <bool b> ~MyContainer();
};

int main()
{
    cout << "before MyContainer<vector<int>>" << endl;
    {
        vector<int> int_vec;
        MyContainer<vector<int>> int_vec_container(int_vec);
    }
    cout << "after MyContainer<vector<int>>" << endl;
    cout << "before MyContainer<int>" << endl;
    {
        MyContainer<int> int_container(1);
    }
    cout << "after MyContainer<int>" << endl;
}


это дает:

before MyContainer<vector<int>>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the St6vectorIiSaIiEE object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<vector<int>>
before MyContainer<int>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the i object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<int>

Ответы [ 2 ]

2 голосов
/ 31 марта 2020

У вас есть ошибка в реализации has_clear:

template <typename U, size_t (U::*)() const> struct SFINAE {
    };            //  ^^^^^^^^^^^^^^^^^^^^^

std::vector::clear возвращает void и не может быть const. Итак:

template <typename U, void (U::*)()> struct SFINAE {
        };
2 голосов
/ 31 марта 2020

Я не знаю, в чем проблема с вашей реализацией has_clear, но ее можно заменить этой значительно упрощенной, работающей реализацией с использованием более современных функций SFINAE / type_traits:

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

template<typename T>
struct has_clear<
    T,
    std::enable_if_t<
        std::is_same_v<decltype(&T::clear), void (T::*)()> ||
        std::is_same_v<decltype(&T::clear), void (T::*)() noexcept>
    >
> : std::true_type {};

И для удобство:

template<typename T>
constexpr bool has_clear_v = has_clear<T>::value;

В сочетании с if constexpr вы можете очень просто и просто решить, какой путь к коду запускать, когда другие не смогут скомпилироваться. Например:

template<typename T>
void maybe_clear(T t){
    if constexpr (has_clear_v<T>){
        // only compiled when T has a non-static clear() method
        std::cout << "clearing " << typeid(T).name() << '\n';
        t.clear();
    } else {
        // only compiled when T does not have a non-static clear() method
        std::cout << "doing nothing with " << typeid(T).name() << '\n';
    }
}

Я считаю, что это позволяет добиться того, чего вы хотите, но исправить, если я неправильно понял. Это решение стоит C ++ 17.

Live Demo

...