Home > Enterprise >  Function template is not considered when it is needed to be called
Function template is not considered when it is needed to be called

Time:09-30

Hi all) I have written simple function for deserialization which is capable to deserialize object of any type. Here is prototype of this function:

template <typename T>
T deserialize(bit_reader &aReader) noexcept;

Also I have put this function into header only library. Below you can see source code of this library(for now it consists with one file bits.h only):

//---------------------  file bits.h  ---------------------
#ifndef bits_h
#define bits_h

#include <type_traits>

namespace bits
{
// code below allows to check whether some function or method is defined and can be called
// for details see: https://en.cppreference.com/w/cpp/experimental/is_detected

namespace details
{
template <class Default, class AlwaysVoid, template <class...> class Op,
          class... Args>
struct detector
{
    using value_t = std::false_type;
    using type = Default;
};

template <class Default, template <class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
    using value_t = std::true_type;
    using type = Op<Args...>;
};

}  // namespace details

struct nonesuch
{
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
};

template <template <class...> class Op, class... Args>
using is_detected =
    typename details::detector<nonesuch, void, Op, Args...>::value_t;

template <template <class...> class Op, class... Args>
constexpr inline bool is_detected_v = is_detected<Op, Args...>::value;

template <template <class...> class Op, class... Args>
using detected_t =
    typename details::detector<nonesuch, void, Op, Args...>::type;

template <class Default, template <class...> class Op, class... Args>
using detected_or = details::detector<Default, void, Op, Args...>;

template <class Default, template <class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;

template <class Expected, template <class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

template <class Expected, template <class...> class Op, class... Args>
constexpr inline bool is_detected_exact_v =
    is_detected_exact<Expected, Op, Args...>::value;

template <class To, template <class...> class Op, class... Args>
using is_detected_convertible =
    std::is_convertible<detected_t<Op, Args...>, To>;

template <class To, template <class...> class Op, class... Args>
constexpr inline bool is_detected_convertible_v =
    is_detected_convertible<To, Op, Args...>::value;


template <typename T>
struct tag_t
{
};

template <typename T>
constexpr tag_t<T> tag{};

class bit_reader
{};

template <class T>
using deserializable_t = decltype(deserialize(std::declval<bit_reader &>(), tag<T>));

template <typename T>
inline constexpr bool is_deserializable_v = is_detected_v<deserializable_t, T>;

template <typename T>
T deserialize(bit_reader &aReader) noexcept
{
    if constexpr (is_deserializable_v<T>)
    {
        return deserialize<T>(aReader, tag<T>);
    }
    else
    {
        static_assert(is_deserializable_v<T>, "Function 'deserialize' is not found.");
    }
    return T{};
}
}  // namespace bits

#endif /* bits_h */

For example to deserialize uint16_t it should be enough to write something like this:

bit_reader reader{};
uint16_t value = deserialize<uint16_t>(reader);

Also I would like that user could provide his own implementation of deserialization function for any type without necessity to make any changes to the library. For example if my library has not implementation for deserialization of uint16_t then user can add it in his own file like so:

//---------------------  file user.cpp  ---------------------
#include <cstdint>
#include <iostream>
#include "bits.h"

namespace bits
{
template <typename T>
using enable_if_uint16_t =
    std::enable_if_t<std::is_same_v<std::decay_t<T>, uint16_t>, T>;

// user difined deserialization for uint16_t
template <typename T>
T deserialize(bit_reader &, tag_t<enable_if_uint16_t<T>>) noexcept
{
    uint16_t value{10};
    return value;
}
}  // namespace bits

using bit_reader = bits::bit_reader;

int main(int, char **)
{
    bit_reader reader{};
    uint16_t restored = bits::deserialize<uint16_t>(reader);
    std::cout << "restored: " << restored << '\n' << std::endl;
    return 0;
}

The problem is that condition if constexpr (is_deserializable_v<T>) in function template <typename T> T deserialize(bit_reader &aReader) noexcept never returns true.

By the way I have managed to make it work by delivering deserialized value via input parameter as opposed to return value. After corresponding changes function:

...
// user difined deserialization for uint16_t
template <typename T>
T deserialize(bit_reader &, tag_t<enable_if_uint16_t<T>>) noexcept
...

turns into:

...
// user difined deserialization for uint16_t
template <typename T>
void deserialize(T&, bit_reader &, tag_t<enable_if_uint16_t<T>>) noexcept
...

But first prototype when deserialized value is delivered to a user via return value seems to me more natural. Is it possible to make condition if constexpr (is_deserializable_v<T>) return true whithout changing the way the deserialized value is delivered to the call site? Take into account that I use C 17.

CodePudding user response:

The problem here is that the compiler has trouble in deducing the template the type T from tag_t<enable_if_uint16_t<T>>, I suggest you put the SFINAE part in the return type since tag<T> is deducible.

// user difined deserialization for uint16_t
template <typename T>
enable_if_uint16_t<T> deserialize(bit_reader &, tag_t<T>) noexcept

Demo

  • Related