2015-11-24 56 views
4

我想在我的代码中进行编译时检查,以确保给定的类超载()运算符,该运算符使用const char *size_t作为参数,并且其返回类型为无符号整数。功能函数的编译时检查

我试图从StackOverflow上采取了一些代码片段,但我不满意我已经写了解决方案:

#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(); 
} 

很抱歉的代码的长度,我试图保持它尽可能小。

这是我不喜欢我的代码:

  1. 如果我重载()操作时,在参数表替代intsize_t,有在编译没有错误,因为size_t可隐含地投射到int

  2. 如果签名不正确(例如,在重载运算符时删除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’ in ‘class 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’ in ‘class 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"); 
    
  3. 我想有一个调用static_assert,是这样的:

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

我该如何做到这一点?谢谢你的帮助。

我使用C++ 11和G ++编译4.8

回答

1

您可以使用此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, ""); 

Online demo

您可以检查完整的实施callable_traitshere

+0

感谢布莱恩,我取得了很大的进步你' callable_traits'。我注意到,当我注释掉'Hash'的'()'操作符重载时,我收到很多错误消息。在这种情况下有什么办法可以提高编译器的输出吗? – Antonin

+0

我设法找到解决方案。再次感谢Bryan! – Antonin

+0

@Antonin:你可以分享这个解决方案吗? – Eric

0

我被要求分享我的最终代码。这取决于Bryan Chen的答案,我已经接受了。在下面的代码中,我非常努力地从编译器中得到干净的错误消息(在我的例子中是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;