"Conversion" from type to same type is causing error
Asked Answered
B

2

7

I have a template function where an enum type is converted to its underlying type which works fine, but I wrote an overload which should take an integral number and return itself and it give me an error that int is not an enumeration type. In my template, this should have been filtered out. What is wrong?

Here is the template code:

  template <typename TT>
  static constexpr auto get_value(TT t)
    -> typename std::enable_if<!std::is_enum<TT>::value, TT>::type
    {
      return t;
    }

  template <typename TT>
  static constexpr auto get_value(TT t)
    -> typename std::enable_if<std::is_enum<TT>::value, typename std::underlying_type<TT>::type>::type
    {
      return (typename std::underlying_type<TT>::type)t;
    }

Demo

Brehm answered 8/9, 2015 at 14:31 Comment(6)
I don't know if underlying_type is SFINAE-friendly, but there's a workaround for thatCitole
What? I see it works, but what is going on here that makes it work? And why wouldn't underlying_type by SFINAE friendly?Brehm
The instantiation of std::underlying_type<TT>::type is deferred, so that enable_if can fail first. By SFINAE-friendly I mean that any substitution failure happens only in immediate context (if it happens inside underlying_type itself it's not SFINAE-friendly).Citole
@PiotrSkotnicki No, it's not SFINAE-friendly, it has a "Condition" that the argument shall be of enumeration type (=> UB).Noblesse
@dyp, So, when template is instantiated, its template's parameter list is fully evaluated and if the evaluation of any of those parameters fail to generate a valid type or value (i.e. not due to a precondition failure) in the calling context, then SFINE kicks in. Otherwise the compile will fail. Is that right?Brehm
@Brehm SFINAE is an acronym of Substitution Failure Is Not An Error, but the concept it names is Substitution Failure In The Immediate Context Is Not An Error. This restricts how deeply a compiler must be able to inspect the types in the template during substitution. For example, template<class T> struct B : T {}; template<class T> B<T> foo(); then foo<int> will not produce a substitution failure in the immediate context (the base class itself could inherit from other classes, where finally an deeply nested error occurs).Noblesse
R
4

std::underlying_type<TT>::type is being evaluated in std::enable_if even though std::is_enum<TT>::value is false as false is not an error. Since a non enumeration type is being evaluated it is causing an error. If we move the SFINAE into the template parameters we can get the desired overloads and still return the correct type.

template <typename TT, typename std::enable_if<!std::is_enum<TT>::value, TT>::type* = nullptr>
static constexpr auto get_value(TT t) -> TT
{
    return t;
}

template <typename TT, typename std::enable_if<std::is_enum<TT>::value>::type* = nullptr>
static constexpr auto get_value(TT t) -> typename std::underlying_type<TT>::type
{
    return (typename std::underlying_type<TT>::type)t;
}

You can see it working in this Live Example

Radioman answered 8/9, 2015 at 15:19 Comment(3)
So just to make sure I'm understanding this correctly, the 2nd template parameter is an unnamed parameter of type void* if is/is not enum type (depending on what I want) resulting in being created and a non entity if not what I want and would then be rejected?Brehm
@Brehm If std::enable_if succeeds type is void and we can then set a pointer of type to nullptr for SFINAERadioman
@Noblesse I have edited the answer. It should address the actual cause now.Radioman
C
4

By attempting to instantiate std::underlying_type<T> with T that is not an enum type, you are violating a requirement that the Standard imposes on template parameter T:

§ 20.10.7.6 [meta.trans.other]/Table 57:

       Template         |         Condition         |       Comments
------------------------+---------------------------+-----------------------
template <class T>      | T shall be an enumeration | The member typedef
struct underlying_type; | type (7.2)                | type shall name
                        |                           | the underlying type 
                        |                           | of T.

Here's an alternative approach if one doesn't like any additional template parameters:

template <typename TT>
static constexpr auto get_value(TT t)
    -> typename std::enable_if<!std::is_enum<TT>::value, TT>::type
{
    return t;
}

template <typename TT>
static constexpr auto get_value(TT t)
    -> typename std::enable_if<std::is_enum<TT>::value
                             , std::underlying_type<TT>
                >::type::type
{
    return (typename std::underlying_type<TT>::type)t;
}

This way, the instantiation of std::underlying_type<TT> is deferred until the condition in std::enable_if evaluates to true, because a nested type definition is requested for what std::enable_if<B,T>::type returns.

DEMO

Citole answered 8/9, 2015 at 17:54 Comment(2)
So, the requirement is only needed if you get the type std::underlying_type<TT>::type but not necessary for std::underlying_type<TT> itself? Why is that?Brehm
@Brehm std::underlying_type<TT> is not instantiated when only used as a type template argumentCitole

© 2022 - 2024 — McMap. All rights reserved.