C++ 有没有更严格的泛型函子参数的方法?

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){

所以我在看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;
}
模板
无效调用(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
 }