skip to Main Content

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.

2

Answers


  1. 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

    Login or Signup to reply.
  2. The reason is that unlike declval<Serialize>(), declval<T>() is a dependent type and the template keyword is needed if you need to call a templated member function of it.

    The following change may fix the problem:

    template <typename T>
    struct is_serializable<
        T, 
        enable_if_t<
            is_same_v<
                decltype(declval<T>().template serialize<Endian::LITTLE>(declval<gsl::span<uint8_t>>())),
    //                                ^^^^^^^^ add "template" here
                gsl::span<uint8_t>
            > &&
            is_same_v<
                decltype(declval<T>().template serialize<Endian::BIG>(declval<gsl::span<uint8_t>>())),
    //                                ^^^^^^^^ and here
                gsl::span<uint8_t>
            >
        >
    > : true_type {};
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search