Checking functor compilation time

I want in my code has compile-time checking, which ensures that the class overloads the operator ()that the operator receives the parameters const char *and size_tas parameters and that the return type is an unsigned integer.

I tried several code snippets taken from StackOverflow, but I am not satisfied with the solution I wrote:

#include <type_traits>
#include <cstdint>
#include <iostream>
#include <memory>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T>
  static auto test(int)
    -> sfinae_true<decltype(std::declval<T>()(static_cast<const char *>(nullptr), static_cast<size_t>(0u)))>;
  template<class>
  static auto test(long) -> std::false_type;
} // detail::

template<class T>
struct is_functor : decltype(detail::test<T>(0)){ };

template <typename T, typename HashFn,
      typename std::enable_if<std::is_unsigned<T>::value, int>::type = 0>
struct Calculation {
  Calculation() {
    static_assert(is_functor<HashFn>(), "BAD signature");
    typedef typename std::result_of<decltype(&HashFn::operator())(HashFn, const char *, size_t)>::type return_type;
    static_assert(std::is_unsigned<return_type>::value, "BAD return type");
  }

  T output() {
    return static_cast<T>(HashFn()(nullptr, 10));
  }
};

struct Hash {
  uint32_t operator ()(const char *buffer, size_t n) const {
    return 65;
  }
};

int main() {
  Calculation<uint64_t, Hash> c;
  c.output();
}

Sorry for the length of the code, I tried to keep it as small as possible.

Here's what I don't like about my code:

  • If I replace intwith size_tin the parameter list when the operator is overloaded (), no error occurs during compilation, because it size_tcan be implicitly discarded by int.

  • (, const ), . , , , .

    rty.cpp: In instantiation of ‘Calculation<T, HashFn, <anonymous> >::Calculation() [with T = long unsigned int; HashFn = Hash; typename std::enable_if<std::is_unsigned<_Tp>::value, int>::type <anonymous> = 0]’:
    rty.cpp:41:31:   required from here
    rty.cpp:24:5: error: static assertion failed: BAD signature
     static_assert(is_functor<HashFn>(), "BAD signature");
     ^
    rty.cpp:25:104: error: no type named ‘type’ inclass std::result_of<unsigned int (Hash::*(Hash, const char*, long unsigned int))(char*, long unsigned int) const>’
     typedef typename std::result_of<decltype(&HashFn::operator())(HashFn, const char *, size_t)>::type return_type;
                                                                                                        ^
    rty.cpp:26:75: error: no type named ‘type’ inclass std::result_of<unsigned int (Hash::*(Hash, const char*, long unsigned int))(char*, long unsigned int) const>’
     static_assert(std::is_unsigned<return_type>::value, "BAD return type");
    
  • static_assert, :

    static_assert(is_correct_functor<HashFn>(), "BAD implementation");
    

? .

++ 11 g++ 4.8

+4
3

callable_traits std::is_same static_assert

// callable_traits

namespace detail {
    template <class ReturnType, class... Args>
    struct callable_traits_base
    {
        using return_type = ReturnType;
        using argument_type = std::tuple<Args...>;

        template<std::size_t I>
        using arg = typename std::tuple_element<I, argument_type>::type;
    };
}

template <class T>
struct callable_traits : callable_traits<decltype(&T::operator())>
{};

// lambda / functor
template <class ClassType, class ReturnType, class... Args>
struct callable_traits<ReturnType(ClassType::*)(Args...) const>
: detail::callable_traits_base<ReturnType, Args...>
{};

struct Hash {
  uint32_t operator ()(const char *buffer, size_t n) const {
    return 65;
  }
};

static_assert(std::is_same<callable_traits<Hash>::return_type, uint32_t>::value, "");
static_assert(std::is_same<callable_traits<Hash>::argument_type, std::tuple<const char *, size_t>>::value, "");

-

callable_traits

+1

. , . ( g++ 4.8) "" :

  • HashFn
  • () ( , unsigned int)
  • ()

, , , .

#include <iostream>
#include <type_traits>
#include <functional>

template <typename T>
struct defines_functor_operator
{
  typedef char (& yes)[1];
  typedef char (& no)[2];

  // we need a template here to enable SFINAE
  template <typename U> 
  static yes deduce(char (*)[sizeof(&U::operator())]);
  // fallback
  template <typename> static no deduce(...);

  static bool constexpr value = sizeof(deduce<T>(0)) == sizeof(yes);
};

// callable_traits

namespace detail {
  template <class ReturnType, class... Args>
    struct callable_traits_base
  {
    using return_type = ReturnType;
    using argument_type = std::tuple<Args...>;

    template<std::size_t I>
    using arg = typename std::tuple_element<I, argument_type>::type;
  };
}

template <class T>
struct callable_traits : callable_traits<decltype(&T::operator())>
{};

// lambda / functor
template <class ClassType, class ReturnType, class... Args>
struct callable_traits<ReturnType(ClassType::*)(Args...) const>
  : detail::callable_traits_base<ReturnType, Args...>
{};

struct Hash {
  uint32_t operator ()(const char *buffer, size_t n) const {
    return 65;
  }
};

template <bool functor, typename H>
struct HashChecker {
  static bool constexpr valid_hash = false;
};

template <typename H>
struct HashChecker<true, H> {
private:
  typedef typename callable_traits<H>::return_type return_type;
  typedef typename callable_traits<H>::argument_type argument_type;

  static bool constexpr v1 = std::is_unsigned<return_type>::value;
  static bool constexpr v2 =
    std::is_same<argument_type, std::tuple<const char *, size_t>>::value;

  static_assert(v1, "Invalid return type for HashFn");
  static_assert(v2, "Invalid parameters for HashFn");

protected:
  static bool constexpr valid_hash = v1 && v2;

};

template <typename T, typename HashFn,
      typename std::enable_if<std::is_unsigned<T>::value, int>::type = 0>
struct Calculation
  : HashChecker<defines_functor_operator<HashFn>::value, HashFn> {

  typedef HashChecker<defines_functor_operator<HashFn>::value, HashFn> HC;

  static_assert(defines_functor_operator<HashFn>::value,
        "HashFn needs to overload '()' operator");

  Calculation() {
  }

  template <typename U = T>
  typename std::enable_if<HC::valid_hash, U>::type output() {
    return static_cast<U>(HashFn()(nullptr, 10));
  }

  template <typename U = T>
  typename std::enable_if<!HC::valid_hash, U>::type output() {
    return static_cast<U>(0u);
  }

};

int main()
{
  Calculation<uint64_t, Hash> c;
  c.output();
  return 0;
}
0

:

template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };

, :

struct Hash {
  uint32_t operator ()(const char *buffer, size_t n) const {
    return 65;
  }
};

bool is_valid_fcn = is_callable<Hash,uint32_t(const char*, size_t)>::value;
0

Source: https://habr.com/ru/post/1617168/


All Articles