有没有办法有更严格的通用仿函数参数?

时间:2014-05-30 21:41:36

标签: 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){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更复杂,这可能会导致难以解析的混淆错误。我认为,如果有办法强制调用者只传递具有特定签名的函子和函数,那么这一切都将被避免。这可能吗?

2 个答案:

答案 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

第三次尝试(感谢@dyp的建议)

编辑:通过将 #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)

嗯,我有个主意。 (它至少需要C ++ 11。)

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,但SomeFunctorfinal。因此,如果编译器在编译时知道其类型,则可以通过静态绑定调用来优化调用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;