0

I have been playing around with SFINAE applied to the "has_member" type of structs as described here.

So I was trying to use some of the features of c++11 to make these solutions simpler. Having some problems with checking for existence of an enum, but I can't seem to locate my logic error here. Code is:

#include<iostream>

template<typename T >
struct has_enum_name
{
private:
  // ***** this is the line that isn't working *****
  // If U has an enum "name", then I expect is_enum to return true…
  // Then I expect enable_if<>::type to be std::true_type…etc. This isn't what happens.
  template<typename U> static auto test(int) -> decltype(
    std::enable_if<std::is_enum< typename U::name >::value, std::true_type>::type() );

  template<typename U> static auto test(...) -> std::false_type;
public:
  static constexpr bool value = 
    !(std::is_same<decltype(test<T>(0)),std::false_type>::value);

  static constexpr bool is_enum()
  {
    return std::is_enum<typename T::name>::value;
  }
};

template<typename T >
struct has_enum_name_2
{
private:
  template<typename U> static auto test(int) -> decltype( 
    std::enable_if<true, std::true_type>::type() );

  template<typename U> static auto test(...) -> std::false_type;
public:
  static constexpr bool value = 
    !(std::is_same<decltype(test<T>(0)),std::false_type>::value);
};

struct Foo
{
  enum class name
  {
    enum1,
    enum2
  };
};

int main()
{
  std::cout<<"std::is_enum<Foo::name>::value = "<<std::is_enum<Foo::name>::value<<std::endl;
  std::cout<<"has_enum_name<Foo>::value      = "<<has_enum_name<Foo>::value<<std::endl;
  std::cout<<"has_enum_name<Foo>::is_enum()  = "<<has_enum_name<Foo>::is_enum()<<std::endl;
  std::cout<<"has_enum_name_2<Foo>::value    = "<<has_enum_name_2<Foo>::value<<std::endl;
}

Running this using gcc 4.9.2 gives

$ ./a.out 
std::is_enum<Foo::name>::value = 1
has_enum_name<Foo>::value      = 0
has_enum_name<Foo>::is_enum()  = 1
has_enum_name_2<Foo>::value    = 1

The first output line verifies that the std::is_enum works correctly for the Foo::name enum.

The second line outputs the results of struct constexpr "has_enum_name::value". I am just trying to use std::enable_if in conjunction with std::is_enum to make the decltype return something…in this case std::true_type(). As you can see, the output returns false…so the line:

template<typename U> static auto test(int) -> decltype( 
  std::enable_if<std::is_enum< typename U::name >::value, std::true_type>::type() );

isn't working out like i think it should. I think that is_enum should return true, which means that enable_if should return true_type, and decltype should return true_type().

Next output is just a check to see if std::is_enum<> is working correctly in the struct…it is.

Next output is just a copy of the "has_enum_name" struct, but I replaced the is_enum<> in the suspect line with a hard coded "true"…and it works correctly.

So for some reason, it appears that is_enum is not working returning true. Any ideas what I am doing wrong here?

Community
  • 1
  • 1
doc07b5
  • 600
  • 1
  • 7
  • 18

1 Answers1

2

When you encounter a problem like this, let the compiler help you. Remove the (...) overload to force a compilation error, and see what GCC tells you:

test.cc: In instantiation of ‘constexpr const bool has_enum_name<Foo>::value’:
test.cc:51:71:   required from here
test.cc:18:33: error: no matching function for call to ‘has_enum_name<Foo>::test(int)’
     !(std::is_same<decltype(test<T>(0)),std::false_type>::value);
                                 ^
test.cc:18:33: note: candidate is:
test.cc:12:36: note: template<class U> static decltype (std::enable_if<std::is_enum<typename U::name>::value, std::integral_constant<bool, true> >::type()) has_enum_name<T>::test(int) [with U = U; T = Foo]
   template<typename U> static auto test(int) -> decltype(
                                    ^
test.cc:12:36: note:   template argument deduction/substitution failed:
test.cc: In substitution of ‘template<class U> static decltype (std::enable_if<std::is_enum<typename U::name>::value, std::integral_constant<bool, true> >::type()) has_enum_name<T>::test(int) [with U = Foo]’:
test.cc:18:33:   required from ‘constexpr const bool has_enum_name<Foo>::value’
test.cc:51:71:   required from here
test.cc:13:83: error: dependent-name ‘std::enable_if<std::is_enum<typename T::name>::value, std::integral_constant<bool, true> >::type’ is parsed as a non-type, but instantiation yields a type
     std::enable_if<std::is_enum< typename U::name >::value, std::true_type>::type() );
                                                                                   ^
test.cc:13:83: note: say ‘typename std::enable_if<std::is_enum<typename T::name>::value, std::integral_constant<bool, true> >::type’ if a type is meant

That's exactly what's wrong: you merely forgot to add typename. T::type() could be valid if type is a non-type: it could just be a function call. That's how the compiler is parsing it.

By the way, decltype(typename T::type()) seems somewhat pointless unless you're specifically trying to check whether the type is default-constructible, and that's not what you're going for here. You can simply use typename T::type directly, like so:

template<typename U> static auto test(int) ->
  typename std::enable_if<std::is_enum<typename U::name>::value, std::true_type>::type;

and if you had tried that without the typename, you would've immediately got a useful error message.

Your initialisation of value is also needlessly complicated, since you already know you're dealing with false_type or true_type: both of those have a value member you can use.

static constexpr bool value = decltype(test<T>(0))::value;
  • Hi, Thanks for the tip on removing the (…). I guess the real question should have been "how do I debug SFINAE?", and that certainly helps! The thing I am still confused about is why the "std::enable_if::type" does not need the typename, and the "std::enable_if::value, std::true_type>::type;" does. – doc07b5 Jan 20 '15 at 16:38
  • 1
    @doc07b5 `std::enable_if::type` is not dependent on a template parameter, so the compiler can see that it is a type when parsing the template. `std::enable_if::value, std::true_type>::type` depends on the template parameter `U`, so you need to tell the compiler that you are expecting it to always yield a type with the `typename` keyword. – Casey Jan 20 '15 at 18:01