Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing decltype and enable_if

Tags:

c++

c++11

It seems that using decltype with SFINAE enable_if isn't straightforward. I tried writing the go using enable_if in three different ways. All of them failed with a compiler error (With GCC literally: "error: 'thing' is not a member of 'foo'" and an instantiation context).

#include <type_traits>

struct foo {
  enum { has_thing = false };
};

struct bar {
  enum { has_thing = true };
  static int thing() { return 0; }
};

template <typename T>
struct Test {
  /*auto go(typename std::enable_if<T::has_thing, int>::type=0) 
  -> decltype(T::thing()) {
    return T::thing();
  }*/

  /*typename std::enable_if<T::has_thing, decltype(T::thing())>::type go() {
    return T::thing();
  }*/

  template <bool B=T::has_thing, typename std::enable_if<B, int>::type = 0>
  auto go() -> decltype(T::thing()) {
     return T::thing();
  }
};

int main() {
  Test<bar> b;
  Test<foo> f;
}

I can see what the problem is - decltype needs to happen before the enable_if even gets a chance to rule out the function. The question that remains is how to work around and get similar behaviour? Is there a simple, generic way to do this without resorting to writing has_thing trait to use in the enable_if?

Tested with both G++ 4.7 and clang++ 3.0.

like image 669
Flexo Avatar asked Jul 02 '12 11:07

Flexo


1 Answers

SFINAE will work if the go method is a template method:

template <typename T>
struct Test {
  template <class U = T>
  auto go() -> decltype(U::thing()) {
     return T::thing();
  }
};

You could also use the has_thing value, but there's no need to do it, since SFINAE will take care of that on the above example:

template <class U = T, typename std::enable_if<U::has_thing, int>::type = 0>
auto go() -> decltype(U::thing()) {
   return T::thing();
} 

Then:

int main() {
  Test<bar> b;
  Test<foo> f;

  b.go(); // Works!
  f.go(); // Fails!
}
like image 96
mfontanini Avatar answered Oct 06 '22 20:10

mfontanini