SFINAE 比赛没有按预期进行

SFINAE matches don't go as expected

我在 ubuntu 上同时使用 g++ 7.5.0 和 clang 6.0.0 来尝试根据对象的方法存在自动调度函数调用的 SFINAE 函数,结果并不像预期。

我期望的是,对于vector的容器,应该在容器的析构函数中调用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>

我不知道你的实现有什么问题 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

您在 has_clear 的实现中有错误:

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

std::vector::clear returns void 而不能是 const。所以:

template <typename U, void (U::*)()> struct SFINAE {
        };