I have a code that conditionaly modifies input type based on type predicate, like this:
template <typename T>
using result = std::conditional_t<type_pred<T>::value, type_cast<T>::type, T>;
It compiles only if T
satisfies type_cast
‘s constraints (regardless of the result of type_pred<T>::value
).
As an example, in the following code std::make_unsigned_t
requres T
to be integral
, so compiler complains if T
is not.
template <typename T>
using make_unsigned_if_integral_t =
std::conditional_t<std::is_integral_v<T>, std::make_unsigned_t<T>, T>;
// this works
static_assert(std::is_same_v<make_unsigned_if_integral_t<int>, unsigned int>);
// compiler complains on this
using expect_float = make_unsigned_if_integral_t<float>;
If type_cast
implementaion does not constraint its param, the approach works well, e.g:
// no constraints on T here, so custom_make_unsigned<float>::type is valid (but never actually used)
template <typename T> struct custom_make_unsigned {
template <typename U> struct impl {
using type = U;
};
template <std::signed_integral U> struct impl<U> {
using type = std::make_unsigned_t<U>;
};
using type = typename impl<T>::type;
};
template <typename T>
using custom_make_unsigned_if_integral_t =
std::conditional_t<std::is_integral_v<T>, typename custom_make_unsigned<T>::type, T>;
static_assert(std::is_same_v<custom_make_unsigned_if_integral_t<float>, float>);
The question is, why does std::conditional
require both branches to be “valid” and what part of the c++ language (or standard library) defines such behaviour?