c++ - 如何使negate_unary使用任何类型?

标签 c++ stl

跟随这个问题:How to negate a predicate function using operator ! in C++?
我想创建一个运算符!可以与从unary_function继承的任何仿函数一起使用。我试过了:

template<typename T>
inline std::unary_negate<T> operator !( const T& pred ) {
 return std::not1( pred );
}

编译器抱怨:
Error 5 error C2955: 'std::unary_function' : use of class template requires template argument list c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 223 1 Graphic
Error 7 error C2451: conditional expression of type 'std::unary_negate<_Fn1>' is illegal c:\program files\microsoft visual studio 10.0\vc\include\ostream 529 1 Graphic
Error 3 error C2146: syntax error : missing ',' before identifier 'argument_type' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 4 error C2065: 'argument_type' : undeclared identifier c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 2 error C2039: 'argument_type' : is not a member of 'std::basic_ostream<_Elem,_Traits>::sentry' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 222 1 Graphic
Error 6 error C2039: 'argument_type' : is not a member of 'std::basic_ostream<_Elem,_Traits>::sentry' c:\program files\microsoft visual studio 10.0\vc\include\xfunctional 230 1 Graphic

任何的想法?

更新
遵循“templatetypedef”解决方案,出现新错误:
Error 3 error C2831: 'operator !' cannot have default parameters c:\visual studio 2010 projects\graphic\graphic\main.cpp 39 1 Graphic
Error 2 error C2808: unary 'operator !' has too many formal parameters c:\visual studio 2010 projects\graphic\graphic\main.cpp 39 1 Graphic
Error 4 error C2675: unary '!' : 'is_prime' does not define this operator or a conversion to a type acceptable to the predefined operator c:\visual studio 2010 projects\graphic\graphic\main.cpp 52 1 Graphic

更新1
完整的代码:
#include <iostream>
#include <functional>
#include <utility>
#include <cmath>
#include <algorithm>
#include <iterator>
#include <string>

#include <boost/assign.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/assign/std/map.hpp>
#include <boost/assign/std/set.hpp>
#include <boost/assign/std/list.hpp>
#include <boost/assign/std/stack.hpp>
#include <boost/assign/std/deque.hpp>

struct is_prime : std::unary_function<int, bool> {
 bool operator()( int n ) const {
  if( n < 2 )
   return 0;
  if( n == 2 || n == 3 )
   return 1;
  if( n % 2 == 0 || n % 3 == 0 )
   return 0;
  int upper_bound = std::sqrt( static_cast<double>( n ) );
  for( int pf = 5, step = 2; pf <= upper_bound; ) {
   if( n % pf == 0 )
    return 0;
   pf  += step;
   step = 6 - step;
  }
  return 1;
 }
};

/*
template<typename T>
inline std::unary_negate<T> operator !( const T& pred, typename T::argument_type* dummy = 0 ) {
 return std::not1<T>( pred );
}
*/

inline std::unary_negate<is_prime> operator !( const is_prime& pred ) {
 return std::not1( pred );
}

template<typename T>
inline void print_con( const T& con, const std::string& ms = "", const std::string& sep = ", " ) {
 std::cout << ms << '\n';
 std::copy( con.begin(), con.end(), std::ostream_iterator<typename T::value_type>( std::cout, sep.c_str() ) );
 std::cout << "\n\n";
}

int main() {
 using namespace boost::assign;
 std::vector<int> nums;
 nums += 1, 3, 5, 7, 9;
 nums.erase( remove_if( nums.begin(), nums.end(), !is_prime() ), nums.end() );
 print_con( nums, "After remove all primes" ); 
}

谢谢,
陈阮

最佳答案

这是一种基于“替代失败不是错误”(SFINAE)原理的方法。这说明在重载解析期间,如果C++编译器尝试实例化模板并遇到问题,则不会触发编译错误。相反,它只是从考虑中删除该特定模板。这里的想法是,如果您重载一个函数(即,许多具有相同名称但参数不同的不同函数),其中一些是模板,而某些不是,则如果其中之一是模板,则永远不会出现编译器错误候选模板功能没有任何意义。

这种特殊的技术可以通过两种不同的方式帮助您。首先,让我们现在假设有一个名为“IsAdaptable”的黑盒子,它可以查看一个类型并告诉我某个特定类型是否为自适应函数。有了这些信息,我们如何使您的operator !函数仅适用于可适应的类型?好吧,使用SFINAE原理,在输入类型不适应的情况下,我们需要以某种方式使模板函数签名无效。有很多方法可以做到这一点,但是一种常见的方法是使用一个称为“启用if”的帮助程序模板。这是enable-if模板的完整实现:

template <bool Condition, typename T> struct EnableIf {
     typedef T type;
};
template <typename T> struct EnableIf<false, T> {
     // Empty
};

这是一个奇怪的模板。它是通过两个参数( bool(boolean) 条件和类型)进行参数化的:如果条件为true,则EnableIf将其第二个参数导出为type;如果条件为false,则根本不导出任何内容。

enable if template的实用程序是它允许您编写仅在某些属性对template参数成立的情况下可用的函数。举例来说,假设您要编写这样的函数:
template <typename T>
ReturnType MyFunction(/* ... arguments ... */) {
    /* ... body ... */
}

但是,仅当某些谓词“谓词”适用于类型T时,才希望此函数可用。然后可以将函数更改为如下所示:
template <typename T>
typename EnableIf<Predicate<T>::value, ActualReturnType>::type MyFunction(/* ... arguments ... */) {
    /* ... body here ... */
}

这里的想法是,如果您使用某个类型参数T调用函数MyFunction,则以下两种情况之一成立。首先,如果Predicate<T>::value为true,则EnableIf最终导出一个称为type的嵌套类型,该类型与ActualReturnType等效,并且该函数照常工作。但是,另一方面,如果Predicate<T>::value为false,则EnableIf实例中没有嵌套的type类型。编译器检测到此情况,并且由于“替换失败不是错误”,因此将其从考虑中删除。如果仍然考虑其他可能的重载,则编译器将选择其中的其他一些重载。如果不是,那么它将报告错误,因为所有可能的MyFunction函数仍然无效。

在您的情况下,想法是这样编写operator !:
template <typename Pred>
typename EnableIf<IsAdaptable<Pred>::value, std::unary_negate<Pred> >::type
operator! (const Pred& p) {
     return std::not1(p);
}

这使用上面的技巧说:“此operator !函数仅适用于可调整函数的类型。”现在到了一半-给了IsAdaptable一个实现,我们就完成了。

问题在于编写IsAdaptable根本不容易。它最终会使用一系列骇人听闻的技巧,以至于会让您哭泣。但是不要害怕!一旦看到了全局,这并不难理解。

我们可以通过一种全新的方式使用SFINAE来使IsAdaptable起作用。这是该想法的简要概述。假设我们有两个函数,它们是另一个的重载,其中一个返回“Yes”类型,而其中一个返回“No”类型。然后,我们编写这些函数,以使"is"版本始终优先于“否”版本,但是"is"版本仅在某些给定类型可调整时才可用。在这种情况下,调用函数时,以下两种情况之一将成立:
  • 有问题的类型是可调整的。在这种情况下,该功能的两个版本均可用,但是"is"版本的优先级高于“否”版本,因此将调用"is"版本。
  • 所讨论的类型不可适应。在这种情况下,可用的函数的唯一版本是“否”版本,因此就是被调用的版本。

  • 但是如何构造这样的函数?事实证明,有一个聪明但并非特别复杂的解决方案:
    template <typename T> Yes TestFunction(typename T::argument_type* argument);
    template <typename T> No  TestFunction(...);
    

    这两个函数都被命名为TestFunction。第一个参数通过某种类型T进行参数化,并以T::argument_type*指针作为参数。第二个参数是varargs参数。现在,如果对于某些类型的T,我们尝试进行以下函数调用:
    TestFunction<T>(NULL);
    

    然后,仅当T具有嵌套在其中的argument_type类型时,此函数的第一个版本才可用。第二个版本始终可用。但是,由于C++重载解析的工作方式,将永远不会在其参数列表更具体的某个函数上选择varargs函数(带有...的函数)。因此,如果Yes内嵌套了T,则上面的表达式的类型为argument_type,否则为No。我们快到了-如果我们能以某种方式检测到返回类型是什么,我们将进行测试以查看T是否适用!

    我们执行此最后一步的方式有些is回。这个想法是,我们将定义类型YesNo,以便它们具有不同的大小:
    typedef char Yes;
    struct No {
        char dummy[32];
    };
    

    现在我们知道了sizeof(Yes) == 1sizeof(No) > 1。将所有这些放在一起,我们得到了IsAdaptable的最终版本:
    template <typename T> struct IsAdaptable {
    private:
        typedef char Yes;
        struct No {
             char dummy[32];
        };
    
        template <typename U> static Yes test(typename U::argument_type*);
        template <typename U> static No  test(...);
    
    public:
        static const bool value = (sizeof(test<T>(0)) == sizeof(Yes));
    };
    

    该结构包含上述所有函数,如果test<T>(0)返回Yes,则导出'true',否则返回false。请注意,因为sizeof实际上并不评估其参数(它只是告诉您它使用了多少字节),所以我们实际上不需要实现这两个函数中的任何一个。

    将绝对的一切放在一起,可以得出最终的解决方案:
    template <bool cond, typename T> struct EnableIf {
      typedef T type;
    };
    template <typename T> struct EnableIf<false, T> {
    
    };
    
    template <typename T> struct IsAdaptable {
    private:
      typedef char Yes;
      struct No {
        char buffer[32];
      };
    
      template <typename U> static Yes test(typename U::argument_type*);
      template <typename U> static No  test(...);
    
    public:
      static const bool result = (sizeof(test<T>(0)) == sizeof(Yes));
    };
    
    template<typename T>                                                                                                                                           
    inline typename EnableIf<IsAdaptable<T>::result, std::unary_negate<T> >::type operator !(const T& pred) {                                                      
      return std::not1( pred );                                                                                                                                    
    }  
    

    希望这可以帮助!

    关于c++ - 如何使negate_unary使用任何类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4598420/

    相关文章:

    c++ - 在 glutKeyboardFunc 中所做的 OpenGL 状态更改不会立即可见

    c++ - std::vector::push_back 参数引用

    c++ - 如何使用 Levenshtein 距离字符串度量

    c++ - 有什么方法可以避免或允许在此线相交算法中除以零?

    c++ - 按字符拆分字符串?

    java - mmap() vs Java MappedByteBuffer 性能?

    c++ - 为 std::map 定义一个使用值而不是键的比较函数

    c++ - array<array<int, M>, N> 的列表初始化

    c++ - 是否可以在 STL set<t> 上实现 next_permutation()

    c++ - int 和元组的静态 STL 映射返回 0