Reputation: 466
I am using both g++ 7.5.0 and clang 6.0.0 on ubuntu to try the SFINAE function of auto dispatching function call according to the method existence of an object and the result doesn't go as expected.
what I expected is that for the container of vector, it should invoke the clear method of the vector in the container's destruction function. for primitive types like int, it does nothing other than printing out messages. but they give both the later one now. I wonder what's wrong here.
#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;
}
it yields:
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>
Upvotes: 1
Views: 65
Reputation: 7212
I don't know what the issue is with your implementation has_clear
, but it can be replaced with this greatly simplified, working implementation using more modern SFINAE/type_traits features:
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 {};
And for convenience:
template<typename T>
constexpr bool has_clear_v = has_clear<T>::value;
Combined with if constexpr
, you can very cleanly and simply decide which code path to run when others would fail to compile. For example:
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';
}
}
I believe this achieves what you want, but correct if I have misunderstood. This solution comes at the cost of requiring C++17.
Upvotes: 2
Reputation: 55395
You have a bug in the implementation of has_clear
:
template <typename U, size_t (U::*)() const> struct SFINAE {
}; // ^^^^^^^^^^^^^^^^^^^^^
std::vector::clear
returns void
and can't be const
. So:
template <typename U, void (U::*)()> struct SFINAE {
};
Upvotes: 2