I can write the following to detect, if an object is serializable.
template <typename T, typename = int>
struct is_serializable : std::false_type {};
template<typename T>
struct is_serializable <T, decltype(std::declval<std::ostream>() << std::declval<T>(), 0)> : std::true_type { };
template <typename T>
constexpr bool is_serializable_v = is_serializable<T>::value;
I do understand that this is expression SFINAE and the overload is only selected if the type of the expression is deducible. Otherwise the default implementation is chosen. But why must the resulting type of the decltype
expression match the type of the default parameter. If I write the following code it does no longer work:
template <typename T, typename = int>
struct is_serializable : std::false_type {};
template<typename T>
struct is_serializable <T, decltype(std::declval<std::ostream>() << std::declval<T>(), 0L)> : std::true_type { };
template <typename T>
constexpr bool is_serializable_v = is_serializable<T>::value;
In the second case, you "specialize"
struct is_serializable <T, long> // For T serializable
but you request in is_serializable_v
the type is_serializable <T, int>
which will never match with your specialization.
Some default parameter explanation:
template <typename T, typename = int>
struct is_serializable : std::false_type {};
// Specializations...
can be seen as
template <typename T, typename = int>
struct is_serializable;
// So writing is_serializable<T> would be is_serializable<T, int>
template <typename T, typename>
struct is_serializable : std::false_type {};
// fallback for any combination not in specialization
// as <NonSerializable, int> <NonSerializable, std::string>
// <Serializable, string>
// but not <Serializable, int> which is part of specializations
// Specializations...