仿函数的编译时检查
Compile-time check of functor
我想在我的代码中进行编译时检查,以确保给定的 class 重载 ()
运算符,该运算符采用 const char *
和 size_t
作为参数,它的 return 类型是无符号整数。
我已经尝试了从 Whosebug 中提取的几个代码片段,但我对我编写的解决方案不满意:
#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();
}
对于代码的长度,我很抱歉,我尽量保持它的长度。
以下是我不喜欢我的代码的地方:
如果我在重载()
运算符时将参数列表中的int
替换为size_t
,编译时不会出错,因为size_t
可以隐式转换为 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’ 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");
我想打一个电话给 static_assert,例如:
static_assert(is_correct_functor<HashFn>(), "BAD implementation");
我怎样才能做到这一点?感谢您的帮助。
我正在使用 C++11 并使用 g++4.8 进行编译
你可以使用这个callable_traits
得到return函子的类型和参数类型,并使用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
here
的完整实现
我被要求分享我的最终代码。它依赖于我已经接受的 Bryan Chen 的回答。在下面的代码中,我非常努力地尝试从编译器(在我的例子中是 g++4.8)获得所有 "possible" 错误情况的清晰错误消息:
HashFn
模板参数不是仿函数
- 重载
()
的return类型不是无符号整数(广义上,不仅仅是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;
}
这是另一个非常简短的解决方案:
template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };
然后您可以像这样使用 if 作为您的特定签名:
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;
我想在我的代码中进行编译时检查,以确保给定的 class 重载 ()
运算符,该运算符采用 const char *
和 size_t
作为参数,它的 return 类型是无符号整数。
我已经尝试了从 Whosebug 中提取的几个代码片段,但我对我编写的解决方案不满意:
#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();
}
对于代码的长度,我很抱歉,我尽量保持它的长度。
以下是我不喜欢我的代码的地方:
如果我在重载
()
运算符时将参数列表中的int
替换为size_t
,编译时不会出错,因为size_t
可以隐式转换为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’ 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");
我想打一个电话给 static_assert,例如:
static_assert(is_correct_functor<HashFn>(), "BAD implementation");
我怎样才能做到这一点?感谢您的帮助。
我正在使用 C++11 并使用 g++4.8 进行编译
你可以使用这个callable_traits
得到return函子的类型和参数类型,并使用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
here
我被要求分享我的最终代码。它依赖于我已经接受的 Bryan Chen 的回答。在下面的代码中,我非常努力地尝试从编译器(在我的例子中是 g++4.8)获得所有 "possible" 错误情况的清晰错误消息:
HashFn
模板参数不是仿函数- 重载
()
的return类型不是无符号整数(广义上,不仅仅是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;
}
这是另一个非常简短的解决方案:
template <typename T, typename Signature>
struct is_callable: std::is_convertible<T,std::function<Signature>> { };
然后您可以像这样使用 if 作为您的特定签名:
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;