c++templatesmetaprogrammingtype-traits

Why does this custom type trait checking for a function template not work?


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:

  1. 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.
  2. 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.
  3. For readability I omitted the std:: prefixes, where applicable. I have added e.g. using std::declval; etc. at the start of the file.
  4. I'm using this gsl implementation and never had any problems with it. The header is included at the start of the file.

Solution

  • Default type of enable_if is void.

    Your template specialization can be used (will be selected as more matched) only if second template parameter of primary template is void:

    template <typename T, typename = void>
    struct is_serializable: false_type {};
    

    You need to use such syntax:

    decltype(declval<T>().template serialize<Endian::LITTLE>
    

    to tell the compiler that <Endian left angle bracket opens template arguments list instead it is less operator.

    Working demo