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)'
}