I have written this type trait with a test case:
template <typename T, typename = int>
struct is_serializable: false_type {};
template <typename T>
struct is_serializable<
T,
enable_if_t<
is_same_v<
decltype(declval<T>().serialize(declval<gsl::span<uint8_t>>())),
gsl::span<uint8_t>
>
>
> : true_type {};
template <typename T>
constexpr bool is_serializable_v = is_serializable<T>::value;
struct Serialize {
gsl::span<uint8_t> serialize(gsl::span<uint8_t> input) {
return input
}
};
static_assert(is_serializable_v<Serialize>, "***");
This works fine.
I then expanded the whole thing by adding a template parameter to choose endianness like so:
enum class Endian {
LITTLE,
BIG
}
template <typename T, typename = int>
struct is_serializable: false_type {};
template <typename T>
struct is_serializable<
T,
enable_if_t<
is_same_v<
decltype(declval<T>().serialize<Endian::LITTLE>(declval<gsl::span<uint8_t>>())),
gsl::span<uint8_t>
> &&
is_same_v<
decltype(declval<T>().serialize<Endian::BIG>(declval<gsl::span<uint8_t>>())),
gsl::span<uint8_t>
>
>
> : true_type {};
template <typename T>
constexpr bool is_serializable_v = is_serializable<T>::value;
struct Serialize {
template <Endian endian>
gsl::span<uint8_t> serialize(gsl::span<uint8_t> input) {
return input
}
};
static_assert(is_serializable_v<Serialize>, "***");
This didn’t work anymore. The type trait returns false.
To see what’s going on I tried
static_assert(
is_same_v<
decltype(declval<Serialize>().serialize<Endian::LITTLE>(declval<gsl::span<uint8_t>>())),
gsl::span<uint8_t>
> &&
is_same_v<
decltype(declval<Serialize>().serialize<Endian::BIG>(declval<gsl::span<uint8_t>>())),
gsl::span<uint8_t>
>,
"***"
)
which is the same thing as the parameter of the enable_if in the trait, and that gave me true
.
But if this is true
the is_serializable_v type trait should also be true
, shouldn’t it? How stupid am I right now?
Disclaimers:
- I am using g++ 10.5.0 with C++17 and can’t go higher on either right now. I have also tried with g++ 13.3.0 and C++20 just to see what happens and it is the same behavior.
- I am on NixOS right now, but the code is for an embedded project running on a Nordic nRF52840. I also tried under Ubuntu 20.04 LTS and, as expected, that did not change anything.
- For readability I omitted the
std::
prefixes, where applicable. I have added e.g.using std::declval;
etc. at the start of the file. - I’m using this gsl implementation and never had any problems with it. The header is included at the start of the file.
2
Answers
Default
type
ofenable_if
isvoid
.Your template specialization can be used (will be selected as more matched) only if second template parameter of primary template is
void
:You need to use such syntax:
to tell the compiler that
<Endian
left angle bracket opens template arguments list instead it is less operator.Working demo
The reason is that unlike
declval<Serialize>()
,declval<T>()
is a dependent type and thetemplate
keyword is needed if you need to call a templated member function of it.The following change may fix the problem:
I suspected the compiler should have shown another message in the form of "expected primary-expression before …". If that is the case, it is yet another example why the full error message should always be posted.