34

Here's what I'm trying to do:

template <typename T> struct Model
{
    vector<T> vertices ;

    #if T has a .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }
    #endif

    #if T has NO .normal member
    void transform( Matrix m )
    {
        each vertex in vertices
        {
          vertex.pos = m * vertex.pos ;
        }
    }
    #endif
} ;

I've seen examples of using enable_if, but I cannot understand how to apply enable_if to this problem, or if it even can be applied.

Community
  • 1
  • 1
bobobobo
  • 64,917
  • 62
  • 258
  • 363
  • 1
    `enable_if` is not used to check if a member exists, rather it is used to remove overloads. – Pubby Dec 09 '12 at 11:15
  • Can't I use it to do something like (suggestion in edit above)? – bobobobo Dec 09 '12 at 11:24
  • No, you're wanting a `static if` which doesn't exist yet. What you want is completely possible, it just won't use syntax like that. – Pubby Dec 09 '12 at 11:30

8 Answers8

35

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 and sizeof 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.
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • That is definitely much better SFINAE than [before](http://stackoverflow.com/a/6324863/111307), where you had to _create_ a member detector for each member type you want to detect. But why do people always jump to SFINAE, I prefer to use [type traits](http://stackoverflow.com/questions/13787490/how-do-you-use-type-traits-to-do-conditional-compilation) to do this type of conditional compilation now. – bobobobo Dec 09 '12 at 13:12
  • 1
    I guess that depends on whether you have more types to check or more members to check? – Dalibor Frivaldsky Aug 01 '14 at 17:42
  • 2
    Type traits only helps if you know the types ahead of time. In your link, you have to know that VertexN is the special class. So they may work in OP's example, but SFINAE works even if you don't know what the other types are going to be, which IMO is much better encapsulation. – Kevin Holt Oct 09 '15 at 14:23
  • 1
    I liked the use of general_ and special_, I found it clearer than using int and long. – tlonuk Sep 15 '16 at 13:16
11

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.

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • The C++20 version is by far the best and most readable solution. I have (ab)used this to effectively add a default-implementation of a member function post-hoc to an existing library of types, without modifying them to inherit a default implementation from a base class: `template auto call_member(T t) { if constexpr(HasMember) { return t.member(); } else { return default_action(); } }`. (A concept requirement is just a `constexpr` boolean expression, so it can be used in `if constexpr` like that just fine) – Irfy May 21 '20 at 03:24
8

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!

ltjax
  • 15,837
  • 3
  • 39
  • 62
3

This isn't an answer to your exact case, but it is an alternative answer to the question title and problem in general.

#include <iostream>
#include <vector>

struct Foo {
    size_t length() { return 5; }
};

struct Bar {
    void length();
};

template <typename R, bool result = std::is_same<decltype(((R*)nullptr)->length()), size_t>::value>
constexpr bool hasLengthHelper(int) { 
    return result;
}

template <typename R>
constexpr bool hasLengthHelper(...) { return false; }

template <typename R>
constexpr bool hasLength() {
    return hasLengthHelper<R>(0);
}

// function is only valid if `.length()` is present, with return type `size_t`
template <typename R>
typename std::enable_if<hasLength<R>(), size_t>::type lengthOf (R r) {
  return r.length();
}

int main() {
    std::cout << 
      hasLength<Foo>() << "; " <<
      hasLength<std::vector<int>>() << "; " <<
      hasLength<Bar>() << ";" <<
      lengthOf(Foo()) <<
      std::endl;
    // 1; 0; 0; 5

    return 0;
}

Relevant https://ideone.com/utZqjk.

Credits to dyreshark on the freenode IRC #c++.

hiddensunset4
  • 5,825
  • 3
  • 39
  • 61
3
template<
typename HTYPE, 
typename = std::enable_if_t<std::is_same<decltype(HTYPE::var1), decltype(HTYPE::var1)>::value>
>
static void close_release
(HTYPE* ptr) {
    ptr->var1;
}

Using enable_if and decltype to let compiler to check variable, hope to help.

vrqq
  • 448
  • 3
  • 8
  • 1
    `std::is_same::value` could be more simply written as `std::is_pointer::value`. Plus, adding the `&` avoids a compiler error when checking for the presence of a function. – Alex Henrie Jan 30 '21 at 09:34
2

While C++20's requires keyword has been mentioned, the code that's provided is still too complex for your needs, requiring the creation of a separate function for each case. Here's much simpler code for your use case, where a single function implementation suffices:

template <typename T> struct Model
{
   vector<T> vertices ;

   void transform( Matrix m )
   {
      each vertex in vertices
      {
         vertex.pos = m * vertex.pos ;
         if constexpr (requires { &vertex.normal; })
            vertex.normal = m * vertex.normal ;
      }
   }
} ;

Notes:

  • All the trick is on the if constexpr line. I've left your pseudo code as is, but removed the redundancy and added the if constexpr line.

  • The requires expression I've added simply attempts to access the address of the normal member, and evaluates to false if the expression is invalid. You could really use any expression that will succeed if normal is defined and fail if it's not.

  • For classes that do have normal, make sure the member is accessible from this code (e.g. it's either public or an appropriate friendship is specified). Otherwise the code would ignore the normal member as if it didn't exist at all.

  • See the "Simple requirements" section at https://en.cppreference.com/w/cpp/language/constraints for more information.

Arda
  • 757
  • 10
  • 18
1

I know that it's little late, however...

typedef int Matrix;

struct NormalVertex {
    int pos;
    int normal;
};

struct Vertex {
    int pos;
};

template <typename T> struct Model
{
    typedef int No;
    typedef char Yes;

    template<typename U> static decltype (declval<U>().normal, Yes()) has_normal(U a);
    static No has_normal(...);

    vector<T> vertices ;

    template <typename U = T>
    typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(Yes), void>::type
    transform( Matrix m )
    {
        std::cout << "has .normal" << std::endl;
        for (auto vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }

    template <typename U = T>
    typename enable_if<sizeof(has_normal(declval<U>())) == sizeof(No), void>::type
    transform( Matrix m )
    {
        std::cout << "has no .normal" << std::endl;
        for (auto vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
        }
    }
} ;

int main()
{
    Matrix matrix;
    Model <NormalVertex> normal_model;

    Vertex simple_vertex;
    Model <Vertex> simple_model;

    simple_model.transform(matrix);
    normal_model.transform(matrix);

    return 0;
}
misterx527
  • 97
  • 5
1

I had a similar issue and my solution was to use boost's BOOST_TTI_HAS_MEMBER_DATA macro.

#include <boost/tti/has_member_data.hpp>

BOOST_TTI_HAS_MEMBER_DATA(normal)

template <typename T> struct Model
{
    vector<T> vertices;
    static constexpr bool hasNormal = has_member_data_normal<T, double>::value;

    template<bool B = hasNormal, std::enable_if_t<B, int> = 0>
    void transform( Matrix m )
    {
        for(auto&& vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
          vertex.normal = m * vertex.normal ;
        }
    }
    
    template<bool B = hasNormal, std::enable_if_t<!B, int> = 0>
    void transform( Matrix m )
    {
        for(auto&& vertex : vertices)
        {
          vertex.pos = m * vertex.pos ;
        }
    }
};

If you don't want to be dependent on boost, then you can use @ltjax's answer to create your own has_member_data_normal struct.

SpuuF
  • 11
  • 1