Jonathan Mee
I want to use [`std::underlying_type`](https://en.cppreference.com/w/cpp/types/underlying_type) with [`std::enable_if`](https://en.cppreference.com/w/cpp/types/enable_if) to conditionally cause [Substitution Failure Is Not An Error (SFINAE)](https://en.cppreference.com/w/cpp/language/sfinae). I'm using this function to conditionally allow either `unsigned int` parameters or enum parameters whose underlying type is `uint8_t`:
template <typename T>
std::enable_if_t<(std::is_enum_v<T> && std::is_same_v<std::underlying_type_t<T>, uint8_t>) || std::is_same_v<T, unsigned int>> Foo(const T param) {
std::cout << static_cast<int>(param) << std::endl;
}
However I'm getting the error when calling `Foo(13U)`:
> error: no matching function for call to `Foo(unsigned int)`
How can I change the `std::enable_if_t` parameter to make this work as intended?
[**Live Example**](http://coliru.stacked-crooked.com/a/ef210af489da6d12)
Top Answer
Jonathan Mee
The problem here is the [`std::underlying_type`](https://en.cppreference.com/w/cpp/types/underlying_type) struct template parameter must be an enumeration type:
> Otherwise, if `T` is not an enumeration type, there is no member `type`
This means calling `std::underlying_type_t<int>` is a compilation error. Short circuiting logical-and operators does not prevent template evaluation. To avoid this we'll need 2 things:
1. [`std::conditional`](https://en.cppreference.com/w/cpp/types/conditional) to optionally use `underlying_type`
1. And an alternative structure which has a `type` member which could be returned instead, I've chosen `template<typename... Ts> struct make_void { typedef void type; }` from https://en.cppreference.com/w/cpp/types/void_t
Using these 2 `Foo` can be written so that the `type` member of `std::underlying_type` is only accessed if `std::is_enum` is valid:
template <typename T>
std::enable_if_t<std::is_same_v<typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>, decltype(make_void<T>())>::type, uint8_t> || std::is_same_v<T, unsigned int>> Foo(const T param) {
std::cout << static_cast<int>(param) << std::endl;
}
[**Live Example**](http://coliru.stacked-crooked.com/a/88f31ccbf8914208)
Answer #2
Indi
None of this is necessary anymore, since C++20.
Once of the nice features of `requires` clauses is that if a constraint results in an invalid type or expression, it simply evaluates to `false`.
In other words, if you do this:
```
template<typename T>
auto foo(std::string_view name, T)
{
std::println("\N{PILE OF POO} bad => {}", name);
}
template<typename T>
requires std::same_as<std::underlying_type_t<T>, std::uint8_t>
auto foo(std::string_view name, T)
{
std::println("\N{PINK HEART} good! => {}", name);
}
```
… and then try to call it with something that would make the `requires` clause invalid:
```
enum class good : std::uint8_t {};
enum class bad {};
#define TEST(x) foo(#x, x)
auto main() -> int
{
TEST(good{});
// Has underlying type, but not std::uint8_t:
TEST(bad{});
// Has no underlying type:
TEST(1);
TEST("string");
}
```
… it will still compile, but the constraint will be `false`. And in the case above, it will fall back on the less specialized overload, so [the output will be](https://compiler-explorer.com/z/qjnohrfca):
```text
🩷 good! => good{}
💩 bad => bad{}
💩 bad => 1
💩 bad => "string"
```
So the solution you want is just:
```
template<typename T>
requires std::same_as<T, unsigned int>
or std::same_as<std::underlying_type_t<T>, std::uint8_t>
auto Foo(T param)
{
std::cout << static_cast<int>(param) << "\n";
}
```
[… which works as expected](https://compiler-explorer.com/z/njTqvqWG7).