C++ 有没有更严格的泛型函子参数的方法?
所以我在看stl,它看起来像,例如,在std::transform中,函数对象的参数只是模板参数,因此调用传递的函数对象时发生的具体情况取决于传递的内容:C++ 有没有更严格的泛型函子参数的方法?,c++,inheritance,functor,C++,Inheritance,Functor,所以我在看stl,它看起来像,例如,在std::transform中,函数对象的参数只是模板参数,因此调用传递的函数对象时发生的具体情况取决于传递的内容: template <class UnaryFunc> void call(UnaryFunc f, int c) { std::cout << f(c)+c << std::endl;} int a(int i) { return i; } struct { int operator()(int i){
template <class UnaryFunc>
void call(UnaryFunc f, int c)
{ std::cout << f(c)+c << std::endl;}
int a(int i) { return i; }
struct { int operator()(int i){return i;}} b;
int main()
{
call(a,2); //function call
call(b,2); //operator() method of b call
return 0;
}
模板
无效调用(UnaryFunc f,int c)
{std::cout嗯,我有个主意。(它至少需要C++11。)
如您所知,如果基类的函数是virtual
,则派生类的函数将成为virtual
,即使它没有声明virtual
。这是一个首次尝试使用它的技巧
除了概念
,我们目前最好的方法是观察函数的特性,并对其执行静态断言
#include <iostream>
#include <functional>
#include <type_traits>
/**
* Function traits helpers.
**/
template <typename>
struct fail : std::integral_constant<bool, false> {};
template <typename ReturnType, std::size_t Arity>
struct alias {
static constexpr std::size_t arity = Arity;
using return_type = ReturnType;
}; // alias
/**
* A few useful traits about functions.
**/
template <typename T, typename Enable = void>
struct function_traits {
static_assert(fail<T>::value, "not a function.");
}; // function_traits
/* Top-level functions. */
template <typename R, typename... Args>
struct function_traits<R (*)(Args...)> : alias<R, sizeof...(Args)> {};
/* Const member functions. */
template <typename R, typename Class, typename ...Args>
struct function_traits<R (Class::*)(Args...) const> : alias<R, sizeof...(Args)> {};
/* Non-const member functions. */
template <typename R, typename Class, typename ...Args>
struct function_traits<R (Class::*)(Args...)> : alias<R, sizeof...(Args)> {};
/* operator() overloads. */
template <typename T>
struct function_traits<T, std::enable_if_t<std::is_class<T>::value>>
: function_traits<decltype(&T::operator())> {};
/* std::function. */
template <typename R, typename ...Args>
struct function_traits<std::function<R (Args...)>> : alias<R, sizeof...(Args)> {};
/**
* Example contraints on UnaryFunc.
**/
template <typename UnaryFunc, typename Arg>
void call(UnaryFunc f, Arg arg) {
static_assert(function_traits<UnaryFunc>::arity == 1,
"UnaryFunc must take one parameter.");
static_assert(
std::is_integral<typename function_traits<UnaryFunc>::return_type>::value,
"UnaryFunc must return an integral.");
std::cout << f(arg) + arg << std::endl;
}
int a(int i) { return i; }
struct {
int operator()(int i){ return i; }
} b;
int c(int i, int j) { return i + j; }
std::string d(int) { return ""; }
int main() {
call(a, 2); // function call
call(b, 2); // operator() method of b call
// call(1, 2); // static_assert: "not a function".
// call(c, 2); // static_assert: "UnaryFunc must take one parameter".
// call(d, 2); // static_assert: "UnaryFunc must return an integral".
}
第三次尝试(感谢@dyp的建议)
<> > >编辑:添加<代码>运算符“没有类型”:C++中不可能。“结果警告可能是模糊的”:您的问题陈述含糊不清,我不清楚问题是什么。您可能在寻找概念吗?很抱歉,您的问题完全不清楚。如果您将带有两个参数的函数对象传递到调用()中如果调用更复杂,编译过程中所看到的错误可能没有帮助,如果使用了一个是函子的类型而不是模板类型,那么泛型编程中的这个不可理解的错误消息是一个实际问题,C++概念(Lite)。尝试解决。虚拟函数不是答案,因为它们增加了耦合,通常会降低空间和时间的效率。请原谅我没有用这么好的措词。@dyp基本上回答了我的问题,我将重新措辞,这样当我返回到PCT时,您可以正确回答。您仍然需要检查调用中的UnaryFunc
是从FunctorBase
或类似的东西派生出来的。它增加了耦合。@dyp-Um,这是有意的-使用这样的boost::bind
,…如果有人使用它,我们无法检查UnaryFunc
是否合适。不过我认为它没问题-只需在某些functor上使用FunctorBase
,而h您需要确保。因此,您要添加的唯一安全性是,当我记得从FunctorBase
派生时,我定义了正确的成员函数?请注意,即使您使用std::bind
,您仍然可以非常轻松地将其包装到从FunctorBase
@dyp派生的类中。哦,我不认为是包装。但是,如果我使用包装器,错误消息是从包装器的代码创建的-我认为没有什么好处。即使我们不使用任何技巧,错误消息也是从某处创建的。更简单的检查是f(arg)
是否格式正确。它无法生成同样好的错误消息(“UnaryFunc不是采用Arg类型参数的函数对象”,但它的问题较少,例如重载的运算符()
等。请注意,您不能在调用中采用成员函数,因为您缺少调用该函数的对象(obj->*f
).@dyp——为了提供更好的错误信息,我考虑了第一种方法。但是,我相信我在第二次尝试中做到了两全其美。它检查了f(arg)的有效性
使用C++14的std::result\u of_t
,这不是一个硬错误,而是SFINAE的错误。我还添加了一个测试来显示重载的操作符()
是受支持的。这看起来非常好而且干净。我想知道这里是否真的需要这些特性:模板自动调用\u impl(UnaryFn u,Arg a,void*)->decltype(u(a)+a,void());
也应该是SFINAE失败。@dyp你确实是对的。更新以反映更简洁的版本。
struct FunctorBase
{
#ifndef CHECK_FUNCTOR
virtual int operator ()(int i) = 0;
#endif
};
template <class UnaryFunc>
void call(UnaryFunc f, int c)
{
std::cout << f(c)+c << std::endl;
}
struct SomeFunctor final : public FunctorBase
{
int operator ()(int i) { ... }
};
int main()
{
call(SomeFunctor(), 3);
}
#include <iostream>
#include <functional>
#include <type_traits>
/**
* Function traits helpers.
**/
template <typename>
struct fail : std::integral_constant<bool, false> {};
template <typename ReturnType, std::size_t Arity>
struct alias {
static constexpr std::size_t arity = Arity;
using return_type = ReturnType;
}; // alias
/**
* A few useful traits about functions.
**/
template <typename T, typename Enable = void>
struct function_traits {
static_assert(fail<T>::value, "not a function.");
}; // function_traits
/* Top-level functions. */
template <typename R, typename... Args>
struct function_traits<R (*)(Args...)> : alias<R, sizeof...(Args)> {};
/* Const member functions. */
template <typename R, typename Class, typename ...Args>
struct function_traits<R (Class::*)(Args...) const> : alias<R, sizeof...(Args)> {};
/* Non-const member functions. */
template <typename R, typename Class, typename ...Args>
struct function_traits<R (Class::*)(Args...)> : alias<R, sizeof...(Args)> {};
/* operator() overloads. */
template <typename T>
struct function_traits<T, std::enable_if_t<std::is_class<T>::value>>
: function_traits<decltype(&T::operator())> {};
/* std::function. */
template <typename R, typename ...Args>
struct function_traits<std::function<R (Args...)>> : alias<R, sizeof...(Args)> {};
/**
* Example contraints on UnaryFunc.
**/
template <typename UnaryFunc, typename Arg>
void call(UnaryFunc f, Arg arg) {
static_assert(function_traits<UnaryFunc>::arity == 1,
"UnaryFunc must take one parameter.");
static_assert(
std::is_integral<typename function_traits<UnaryFunc>::return_type>::value,
"UnaryFunc must return an integral.");
std::cout << f(arg) + arg << std::endl;
}
int a(int i) { return i; }
struct {
int operator()(int i){ return i; }
} b;
int c(int i, int j) { return i + j; }
std::string d(int) { return ""; }
int main() {
call(a, 2); // function call
call(b, 2); // operator() method of b call
// call(1, 2); // static_assert: "not a function".
// call(c, 2); // static_assert: "UnaryFunc must take one parameter".
// call(d, 2); // static_assert: "UnaryFunc must return an integral".
}
#include <iostream>
#include <type_traits>
/**
* Useful utilities
**/
template <typename...>
struct success : std::true_type {};
template <typename...>
struct fail : std::false_type {};
/**
* 'is_addable' type_trait.
* Takes two types and tests if they can be added together.
**/
template <typename Lhs, typename Rhs>
success<decltype(std::declval<Lhs>() + std::declval<Rhs>())>
is_addable_impl(void *);
template <typename Lhs, typename Rhs>
std::false_type is_addable_impl(...);
template <typename Lhs, typename Rhs>
struct is_addable : decltype(is_addable_impl<Lhs, Rhs>(nullptr)) {};
/**
* 'call' implementation.
* If the result of unary_fn(arg) can be added to arg, dispatch to the first
* overload, otherwise provide a static asertion failure.
**/
template <typename UnaryFn, typename Arg>
std::enable_if_t<is_addable<std::result_of_t<UnaryFn (Arg)>, Arg>::value,
void> call_impl(UnaryFn unary_fn, Arg arg, void *) {
std::cout << unary_fn(arg) + arg << std::endl;
}
template <typename UnaryFn, typename Arg>
void call_impl(UnaryFn unary_fn, Arg arg, ...) {
static_assert(fail<UnaryFn, Arg>::value,
"UnaryFn must be a function which takes exactly one argument "
"of type Arg and returns a type that can be added to Arg.");
}
template <typename UnaryFn, typename Arg>
void call(UnaryFn unary_fn, Arg arg) {
return call_impl(unary_fn, arg, nullptr);
}
/**
* Tests.
**/
int a(int i) { return i; }
struct {
int operator()(int i){ return i; }
std::string operator()(std::string s){ return s; }
} b;
int c(int i, int j) { return i + j; }
std::string d(int) { return ""; }
int main() {
call(a, 2); // function call
call(b, 2); // operator() method of b call
call(b, "hello"); // operator() method of b call
// call(1, 2); // static_assert fail
// call(c, 2); // static_assert fail
// call(d, 2); // static_assert fail
}
#include <iostream>
#include <type_traits>
template <typename...>
struct fail : std::false_type {};
/**
* 'call' implementation.
* If the result of unary_fn(arg) can be added to arg, dispatch to the
* first overload, otherwise provide a static asertion failure.
**/
template <typename UnaryFn, typename Arg>
auto call_impl(UnaryFn unary_fn, Arg arg, void *)
-> decltype(std::cout << unary_fn(arg) + arg << std::endl, void()) {
std::cout << unary_fn(arg) + arg << std::endl;
}
template <typename UnaryFn, typename Arg>
void call_impl(UnaryFn unary_fn, Arg arg, ...) {
static_assert(fail<UnaryFn, Arg>::value,
"UnaryFn must be a function which takes exactly one argument "
"of type Arg and returns a type that can be added to Arg.");
}
template <typename UnaryFn, typename Arg>
void call(UnaryFn unary_fn, Arg arg) {
call_impl(unary_fn, arg, nullptr);
}
/**
* Tests.
**/
int a(int i) { return i; }
struct {
int operator()(int i){ return i; }
std::string operator()(std::string s){ return s; }
} b;
int c(int i, int j) { return i + j; }
std::string d(int) { return ""; }
int main() {
call(a, 2); // function call
call(b, 2); // operator() method of b call
call(b, "hello"); // operator() method of b call
// call(1, 2); // static_assert fail
// call(c, 2); // static_assert fail
// call(d, 2); // static_assert fail
}