check if member exists using enable_if
You need a meta function to detect your member so that you can use enable_if
. The idiom to do this is called Member Detector. It's a bit tricky, but it can be done!
I know this question already has some answers but I think my solution to this problem is a bit different and could help someone.
The following example checks whether passed type contains c_str()
function member:
template <typename, typename = void>
struct has_c_str : false_type {};
template <typename T>
struct has_c_str<T, void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(declval<T>().c_str())>
{};
template <typename StringType,
typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
bool setByString(StringType const& value) {
// use value.c_str()
}
In case there is a need to perform checks whether passed type contains specific data member, following can be used:
template <typename, typename = void>
struct has_field : std::false_type {};
template <typename T>
struct has_field<T, std::void_t<decltype(T::field)>> : std::is_convertible<decltype(T::field), long>
{};
template <typename T,
typename std::enable_if<has_field<T>::value, T>::type* = nullptr>
void fun(T const& value) {
// use value.field ...
}
UPDATE C++20
C++20 introduced constraints and concepts, core language features in this C++ version.
If we want to check whether template parameter contains c_str
member function, then, the following will do the work:
template<typename T>
concept HasCStr = requires(T t) { t.c_str(); };
template <HasCStr StringType>
void setByString(StringType const& value) {
// use value.c_str()
}
Furthermore, if we want to check if the data member, which is convertible to long
, exists, following can be used:
template<typename T>
concept HasField = requires(T t) {
{ t.field } -> std::convertible_to<long>;
};
template <HasField T>
void fun(T const& value) {
// use value.field
}
By using C++20, we get much shorter and much more readable code that clearly expresses it's functionality.
This has become way easier with C++11.
template <typename T> struct Model
{
vector<T> vertices;
void transform( Matrix m )
{
for(auto &&vertex : vertices)
{
vertex.pos = m * vertex.pos;
modifyNormal(vertex, m, special_());
}
}
private:
struct general_ {};
struct special_ : general_ {};
template<typename> struct int_ { typedef int type; };
template<typename Lhs, typename Rhs,
typename int_<decltype(Lhs::normal)>::type = 0>
void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) {
lhs.normal = rhs * lhs.normal;
}
template<typename Lhs, typename Rhs>
void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) {
// do nothing
}
};
Things to note:
- You can name non-static data members in
decltype
andsizeof
without needing an object. - You can apply extended SFINAE. Basically any expression can be checked and if it is not valid when the arguments are substituted, the template is ignored.