0

I'm getting the error:

Error   C2154   '_Ty': only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type' 

I thought it shouldn't be resolving underlying_type to underlying_type, because I first check whether T is an enum. Here is the code:

template <typename T>
struct Foo
{
    static inline constexpr bool isArgIntegral = std::is_integral<T>::value;
    static inline constexpr bool isArgEnum = std::is_enum_v<T>;

    using integral_underlying_type = std::conditional<isArgEnum, std::underlying_type_t<T>, T>;



};



int main()

    Foo<int> g; // only enumeration type is allowed as an argument to compiler intrinsic type trait '__underlying_type' 
}

So is it the case that in a call to std::conditional, instead of first checking the condition (1st argument), it creates the classes of the 2nd and 3rd arguments regardless of the condition, and hence why I'm getting the error that I can't call underlying_type with an 'int'?

How do I go about getting the integral type of the T template argument, whether it's an integral or an enum?

Edit: My next attempt is to place the typedef in an if constexpr:

if constexpr (std::is_enum_v<T>)
{
    using integral_underlying_type = std::underlying_type_t<T>;
// Now std::underlying_type_t won't be called at all unless T is enum, right?
}
Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • `std::conditional` is a template – and templates provide a separate type for any instantiation. But to be able to determine the instantiation's type, all arguments need to be evaluated... – Aconcagua Mar 08 '21 at 06:49
  • Ah, that makes sense. – Zebrafish Mar 08 '21 at 06:51
  • Might be of interest (though don't think it's a duplicate): https://stackoverflow.com/questions/44550976/how-stdconditional-works – Aconcagua Mar 08 '21 at 06:52
  • On paper you could work around it if you don't use the `underlyong_type_t` utility (since it forces instantiation). Just delay it one step `typename conditional_t, type_identity>::type` – StoryTeller - Unslander Monica Mar 08 '21 at 07:00

2 Answers2

3

Unfortunately, std::integral_underlying_type is not SFINAE friendly until C++20.

You can then delay instantiation of std::underlying_type<T>::type:

template <typename T>
struct Foo
{
    using integral_underlying_type =
        typename std::conditional_t<std::is_enum_v<T>,
                                    std::underlying_type<T>,
                                    type_identity<T>>::type;
};

Notice the double ::type in std::conditional<cond, T1, T2>::type::type (hidden with _t). The extra ::type is done outside the conditional instead of inside (and so the need of type_identity).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

Yes. The problem is that whether the condition isArgEnum is true or false (i.e. whether T is enum or not), std::underlying_type_t<T> has to be specified as the template argument for std::conditional.

You can apply partial specialization like

template <typename T, typename = void>
struct Foo
{};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_enum_v<T>>>
{
    using integral_underlying_type = std::underlying_type_t<T>;
};
template <typename T>
struct Foo<T, std::enable_if_t<std::is_integral_v<T>>>
{
    using integral_underlying_type = T;
};
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • When you write std::enable_if_t> as the second template argument in the specialisation, because you didn't provide the second argument to std::enable_if_t I don't understand what the typedef is, because it's T = void. Then that looks like you wrote "std::enable_if_t, void>" I don't get that. – Zebrafish Mar 08 '21 at 07:11
  • 1
    @Zebrafish `std::enable_if>::type` is void if `T` is an enum. If it's not then it's a substitution failure. But since it's in the immidiate context it's not an error. SFINAE. So when the type does resolve to `void` it will match the base template, which makes the specialization valid. – super Mar 08 '21 at 07:29