Check If Member Exists Using Enable_If

check if member exists using enable_if

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.

Checking a member exists, possibly in a base class, C++11 version

Actually, things got much easier in C++11 thanks to the decltype and late return bindings machinery.

Now, it's just simpler to use methods to test this:

// Culled by SFINAE if reserve does not exist or is not accessible
template <typename T>
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) {
return true;
}

// Used as fallback when SFINAE culls the template method
constexpr bool has_reserve_method(...) { return false; }

You can then use this in a class for example:

template <typename T, bool b>
struct Reserver {
static void apply(T& t, size_t n) { t.reserve(n); }
};

template <typename T>
struct Reserver <T, false> {
static void apply(T& t, size_t n) {}
};

And you use it so:

template <typename T>
bool reserve(T& t, size_t n) {
Reserver<T, has_reserve_method(t)>::apply(t, n);
return has_reserve_method(t);
}

Or you can choose a enable_if method:

template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<has_reserve_method(t), bool>::type {
t.reserve(n);
return true;
}

template <typename T>
auto reserve(T& t, size_t n) -> typename std::enable_if<not has_reserve_method(t), bool>::type {
return false;
}

Note that this switching things is actually not so easy. In general, it's much easier when just SFINAE exist -- and you just want to enable_if one method and not provide any fallback:

template <typename T>
auto reserve(T& t, size_t n) -> decltype(t.reserve(n), void()) {
t.reserve(n);
}

If substitution fails, this method is removed from the list of possible overloads.

Note: thanks to the semantics of , (the comma operator) you can chain multiple expressions in decltype and only the last actually decides the type. Handy to check multiple operations.

Modified code from check if member exists using enable_if is not working

Lhs is deduced to be a reference type in your example, specifically DataWithNormal&. Reference types don't have a nested normal. One way to get around this is to check Lhs when references are stripped:

decltype(std::remove_reference<Lhs>::type::normal)

Lifting Igor's comment, you can also pretend you have an object, since accessing a member of an object works even with a reference:

decltype(std::declval<Lhs>().normal)

Using SFINAE to check if member exists in class based on a template

The template parameters of the template template parameter can not be used in the body.

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

template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};

template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}

And then,

static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);

Online Demo

Using SFINAE to check if member exists in class based on a template

The template parameters of the template template parameter can not be used in the body.

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

template <template <typename> typename EntryType, typename KeyType>
struct has_psl<EntryType<KeyType>, std::void_t<decltype(EntryType<KeyType>::psl)>> : std::true_type
{};

template <template <typename> typename EntryType, typename KeyType, std::enable_if<has_psl<EntryType<KeyType>>::value>>
bool f(EntryType<KeyType> *buffer, size_t offset, EntryType<KeyType> *new_entry) {
return true;
}

And then,

static_assert(has_psl<int>::value == false);
static_assert(has_psl<RawBytesEntry<int>>::value == true);

Online Demo

Selecting a member function using different enable_if conditions

enable_if works because the substitution of a template argument resulted in an error, and so that substitution is dropped from the overload resolution set and only other viable overloads are considered by the compiler.

In your example, there is no substitution occurring when instantiating the member functions because the template argument T is already known at that time. The simplest way to achieve what you're attempting is to create a dummy template argument that is defaulted to T and use that to perform SFINAE.

template<typename T>
struct Point
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
std::cout << "T is int." << std::endl;
}

template<typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
std::cout << "T is not int." << std::endl;
}
};

Edit:

As HostileFork mentions in the comments, the original example leaves the possibility of the user explicitly specifying template arguments for the member functions and getting an incorrect result. The following should prevent explicit specializations of the member functions from compiling.

template<typename T>
struct Point
{
template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is int." << std::endl;
}

template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is not int." << std::endl;
}
};

How to detect whether there is a specific member variable in class?

Another way is this one, which relies on SFINAE for expressions too. If the name lookup results in ambiguity, the compiler will reject the template

template<typename T> struct HasX { 
struct Fallback { int x; }; // introduce member name "x"
struct Derived : T, Fallback { };

template<typename C, C> struct ChT;

template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1];
template<typename C> static char (&f(...))[2];

static bool const value = sizeof(f<Derived>(0)) == 2;
};

struct A { int x; };
struct B { int X; };

int main() {
std::cout << HasX<A>::value << std::endl; // 1
std::cout << HasX<B>::value << std::endl; // 0
}

It's based on a brilliant idea of someone on usenet.

Note: HasX checks for any data or function member called x, with arbitrary type. The sole purpose of introducing the member name is to have a possible ambiguity for member-name lookup - the type of the member isn't important.

Templated check for the existence of a class member function?

Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:

#include <iostream>

struct Hello
{
int helloworld() { return 0; }
};

struct Generic {};

// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };

template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);

public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}

I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.

SFINAE Based on Class Member Existence/Absence

why do we need an extra type (special_/general_) in this method

These are used only for the purpose of allowing the modifyNormal function to be overloaded with different implementations. What is special about them is that special_ uses the IS-A relationship since it inherits from general_. Furthermore, the transform function always calls the modifyNormal overload which takes the special_ type, see the next part.

what does typename int_<decltype(Lhs::normal)>::type = 0 help us do

This is a template argument with a default value. The default value is present so that the transform function doesn't have to specify it, which is important because the other modifyNormal function doesn't have this template parameter. Furthermore, this template parameter is only added to invoke SFINAE.

http://en.cppreference.com/w/cpp/language/sfinae

When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.

So if a failure occurs the modifyNormal function taking the special_ type is removed from the set of overloads to consider. This only leaves the modifyNormal function taking a general_ type and since special_ IS-A general_ type everything still works.

If a substitution failure does not occur then the modifyNormal function using the special_ type will be used since it's a better match.


Note: The general_ type is a struct so the inheritance is public by default, allowing the IS-A relationship without using the public keyword.


Edit:

Can you comment on why we use the elaborate typename int_<decltype(Lhs::normal)>::type mechanism in the first place?

As stated above this is used to trigger the SFINAE behavior. However, it's not very elaborate when you break it down. At it's heart it wants to instantiate a instance of the int_ struct for some type T and it has a type data type defined:

int_<T>::type

Since this is being used in a template the typename keyword needs to be added, see When is the “typename” keyword necessary?.

typename int_<T>::type

Lastly, what is the actual type used to instantiate the int_ struct? That's determined by decltype(Lhs::normal), which reports the type for Lhs::normal. If type Lhs type has a normal data member then everything succeeds. However, if it doesn't then there is a substitution failure and the importance of this is explained above.

std::enable_if to conditionally compile a member function

SFINAE only works if substitution in argument deduction of a template argument makes the construct ill-formed. There is no such substitution.

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!). Among them are also its member templates. Note that T is known then, and !std::is_same< T, int >::value yields false. So it will create a class Y<int> which contains

class Y<int> {
public:
/* instantiated from
template < typename = typename std::enable_if<
std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/

template < typename = typename std::enable_if< true >::type >
int foo();

/* instantiated from

template < typename = typename std::enable_if<
! std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/

template < typename = typename std::enable_if< false >::type >
int foo();
};

The std::enable_if<false>::type accesses a non-existing type, so that declaration is ill-formed. And thus your program is invalid.

You need to make the member templates' enable_if depend on a parameter of the member template itself. Then the declarations are valid, because the whole type is still dependent. When you try to call one of them, argument deduction for their template arguments happen and SFINAE happens as expected. See this question and the corresponding answer on how to do that.



Related Topics



Leave a reply



Submit