Home > front end >  How does std::conditional_t<std::is_enum_v<T>, TypeWhenTrue, TypeWhenFalse> determine ho
How does std::conditional_t<std::is_enum_v<T>, TypeWhenTrue, TypeWhenFalse> determine ho

Time:08-02

It seems that std::conditional_t<B,T,F> doesn't always return the second value, even when B is false. Here's an example:

#include <type_traits>

template<class T>
using UnderlyingType = std::conditional_t< std::is_enum_v<T>, std::underlying_type_t<T>, T >;

template< typename T >
constexpr UnderlyingType<T> toUnderlying( T val )
{
    static_assert( std::is_integral_v<T> || std::is_enum_v<T>, "T must be an enum, or an integral type" );
    return val;
}

enum ExampleEnum {
    ZERO = 0,
    ONE,
    TWO,
    THREE
};

int main()
{
    UnderlyingType<ExampleEnum> underlying = toUnderlying( ZERO ); // compiles and works fine
    UnderlyingType<ExampleEnum> actual = toUnderlying( 0 ); // error: no matching function for call to 'toUnderlying(int)'
}

Then later on in the error message I get:

<source>:4:7: error: no type named 'type' in 'struct std::underlying_type<int>'
    4 | using UnderlyingType = std::conditional_t< std::is_enum_v<T>, std::underlying_type_t<T>, T >;
      |       ^~~~~~~~~~~~~~

Here's the code in godbolt: https://godbolt.org/z/zd5qT94qY

Can anyone explain how it is possible that UnderlyingType is evaluating to the first type, even when the template should be taking an int? And possibly a solution? I also tried replacing std::conditional_t with the implementation on https://en.cppreference.com/w/cpp/types/conditional thinking maybe it was some implementation quirk, but it seems to fail as well.

UPDATE:

Thanks to everyone that answered. I was able to tweak the given answer a bit to allow only specified types. Here's the full code for that for future googlers:

#include <type_traits>
#include <iostream>
#include <iomanip>

template <typename T> 
struct UnderlyingTypeStruct {
    using type = T;
};

template <typename T> requires (!std::is_integral_v<T> && !std::is_enum_v<T>)
struct UnderlyingTypeStruct<T> {
    static_assert( std::is_integral_v<T> || std::is_enum_v<T>, "T must be an enum, or an integral type" ); 
    using type = std::underlying_type_t<T>;
};

template <typename T> requires std::is_integral_v<T>
struct UnderlyingTypeStruct<T> {
    using type = T;
};

template <typename T> requires std::is_enum_v<T>
struct UnderlyingTypeStruct<T> {
    using type = std::underlying_type_t<T>;
};

template<class T>
using UnderlyingType = typename UnderlyingTypeStruct<T>::type;

template< typename T >
constexpr UnderlyingType<T> toUnderlying( T val )
{
    static_assert( std::is_integral_v<T> || std::is_enum_v<T>, "T must be an enum, or an integral type" ); 
    return val;
}

enum ExampleEnum {
    ZERO = 0,
    ONE,
    TWO,
    THREE
};

int main()
{
    UnderlyingType<ExampleEnum> underlying = toUnderlying( ZERO ); // ok
    std::cout << underlying << std::endl;
    UnderlyingType<ExampleEnum> actual = toUnderlying( 0 );  // okstd::cout << actual << std::endl;
    UnderlyingType<const char *> myInvalidString = toUnderlying( "hello world" ); // error
    std::cout << myInvalidString << std::endl;
    return 0;
}

And the link to an updated godbolt: https://godbolt.org/z/KbbM3bdMj

CodePudding user response:

std::conditional doesnt even get to choose one of the types, because the first argument isnt a type. std::underlying_type_t<int> just doesnt exist. From cppreference:

If T is a complete enumeration (enum) type, provides a member typedef type that names the underlying type of T.

Otherwise, the behavior is undefined. (until C 20)

Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. (since C 20)

Wherher UB or not, there is no std::underlying_type_t<int>.

You can use SFINAE such that the specialization that does use std::underlying_type_t<int> gets discarded:

#include <type_traits>
template <typename T, typename = void>
struct UnderlyingType {
    using type = T;
};

template <typename T>
struct UnderlyingType<T,std::enable_if_t<std::is_enum_v<T>>> {
    using type = std::underlying_type_t<T>;
};

template<class T>
using UnderlyingType_t = typename UnderlyingType<T>::type;

template< typename T >
constexpr UnderlyingType_t<T> toUnderlying( T val )
{
    static_assert( std::is_integral_v<T> || std::is_enum_v<T>, "T must be an enum, or an integral type" );
    return val;
}

enum ExampleEnum {
    ZERO = 0,
    ONE,
    TWO,
    THREE
};

int main()
{
    UnderlyingType_t<ExampleEnum> underlying = toUnderlying( ZERO ); // compiles and works fine
    UnderlyingType_t<ExampleEnum> actual = toUnderlying( 0 ); // error: no matching function for call to 'toUnderlying(int)'
}
  • Related