所以我正在查看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){return i;}} b;
int main()
{
call(a,2); //function call
call(b,2); //operator() method of b call
return 0;
}
这很棒,因为它非常通用:它允许你使用像boost::bind()
这样的东西,并将两个函子(如b
)和函数(如a
)传递给同一个函数call
。
然而,这太宽松了。例如,在int (int i, int j)
中调用之前,传递带有签名call
的函数不会导致错误。如果call
更复杂,这可能会导致难以解析的混淆错误。我认为,如果有办法强制调用者只传递具有特定签名的函子和函数,那么这一切都将被避免。这可能吗?
答案 0 :(得分:2)
缺少Concepts
,我们目前最好的方法是观察函数的特征并对它们执行static_assert
。例如,
#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".
}
这种尝试解决了第一种方法的局限性,主要是unary_fn
无法超载的事实。它还添加了一个更受限制的测试,可以添加f(arg)
和arg
的结果。
注意:此处需要{+ 1}}的C ++ 14版本,因为C ++ 11版本不能提供SFINAE行为。
std::result_of_t
编辑:通过将 #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
}
次调用添加到operator<<
,我们会另外测试decltype
的结果也是可打印的。
如果unary_fn(arg) + arg
是一种有用的类型特征,则可以按照第二次尝试将其排除。
如果没有,我们可以在is_addable
中简单地执行SFINAE
内联。更短更清洁。
decltype
答案 1 :(得分:0)
struct FunctorBase
{
virtual int operator ()(int i) = 0;
};
template <class UnaryFunc>
void call(UnaryFunc f, int c)
{
std::cout << f(c)+c << std::endl;
}
struct SomeFunctor final : public FunctorBase
{
virtual int operator ()(int i) override { ... }
};
int main()
{
call(SomeFunctor(), 3);
}
SomeFunctor::operator ()
为virtual
,但SomeFunctor
为final
。因此,如果编译器在编译时知道其类型,则可以通过静态绑定调用来优化调用operator ()
。 (当然,编译器在专门化template <> void call(SomeFunctor, int)
时就知道它的类型。)
call
仍然使用模板来接收仿函数参数,因此它仍然可以接收不继承FunctorBase
的仿函数。 (例如std::bind
,...)
当然,SomeFunctor
有一个不必要的vptr,它可能是不必要的开销。然而,sizeof(void *)
的开销很小 - 除非您需要进行高级优化,否则可以忽略它。
(事实上types cannot have a zero size in most case, even if it is empty。所以我猜这不是开销。)
此外,如果你介意的话,可以减少开销:
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);
}
如您所知,如果基类的函数是virtual
,则派生类的函数变为virtual
,即使它未声明virtual
。这是一个使用它的技巧&gt; o&lt;