9

我有一个SFINAE问题:

在下面的代码中,我希望 C++ 编译器选择专用函子并打印“special”,但它正在打印“general”。

#include <iostream>
#include <vector>

template<class T, class V = void>
struct Functor {
  void operator()() const {
    std::cerr << "general" << std::endl;
  }
};

template<class T>
struct Functor<T, typename T::Vec> {
  void operator()() const {
    std::cerr << "special" << std::endl;
  }
};

struct Foo {
  typedef std::vector<int> Vec;
};

int main() {
  Functor<Foo> ac;
  ac();
}

如何修复它以便自动使用专用结构?请注意,我不想直接将Functorstruct专门化Foo,但我想将其专门化为具有类型的所有类型Vec

PS:我正在使用 g++ 4.4.4

4

3 回答 3

12

很抱歉在最后一个答案中误导了您,我想了一下会更简单。所以我会尝试在这里提供一个完整的解决方案。解决此类问题的一般方法是编写一个特征帮助器模板并将其与enable_if(C++11、boost 或手动实现)一起使用来决定类专业化:

特征

一个简单的方法,不一定是最好的,但写起来很简单:

template <typename T>
struct has_nested_Vec {
    typedef char yes;
    typedef char (&no)[2];
    template <typename U>
    static yes test( typename U::Vec* p );
    template <typename U>
    static no test( ... );

    static const bool value = sizeof( test<T>(0) ) == sizeof(yes);
};

方法很简单,提供两个模板函数,返回不同大小的类型。其中一个采用嵌套Vec类型,另一个采用省略号。对于所有具有嵌套Vec的类型,第一个重载是更好的匹配(省略号是任何类型的最差匹配)。对于那些没有嵌套VecSFINAE 的类型将丢弃该重载,剩下的唯一选项将是省略号。所以现在我们有一个特征来询问任何类型是否有嵌套Vec类型。

启用如果

您可以从任何库中使用它,也可以自己滚动,这非常简单:

template <bool state, typename T = void>
struct enable_if {};

template <typename T>
struct enable_if<true,T> {
    typedef T type;
};

当第一个参数是false时,基本模板是唯一的选项,并且没有嵌套的type,如果条件是true,则enable_if有一个type可以与 SFINAE 一起使用的嵌套。

执行

现在我们需要提供模板和专门化,将 SFINAE 仅用于那些带有嵌套的类型Vec

template<class T, class V = void>
struct Functor {
    void operator()() const {
        std::cerr << "general" << std::endl;
    }
};
template<class T>
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > {
    void operator()() const {
        std::cerr << "special" << std::endl;
    }
};

每当我们Functor用一个类型实例化时,编译器都会尝试使用特化,这将反过来实例化has_nested_Vec并获得一个真值,传递给enable_if. 对于那些值为false,enable_if的类型没有嵌套type类型,因此 SFINAE 中将丢弃特化并使用基本模板。

您的特殊情况

在您的特定情况下,您似乎不需要专门化整个类型而只需要专门化运算符,您可以将三个元素混合为一个元素:aFunctor根据存在性分派到两个内部模板化函数之一的Vec,消除对enable_if和特征类的需要:

template <typename T>
class Functor {
   template <typename U>
   void op_impl( typename U::Vec* p ) const {
      std::cout << "specialized";
   }
   template <typename U>
   void op_impl( ... ) const {
      std::cout << "general";
   }
public:
   void operator()() const {
      op_impl<T>(0);
   }
};
于 2012-07-04T02:26:58.113 回答
4

尽管这是一个老问题,但我认为仍然值得提供更多替代方案来快速修复原始代码。

基本上,问题不在于 SFINAE 的使用(实际上那部分很好),而在于主模板 ( void) 中的默认参数与部分专业化 ( typename T::Vec) 中提供的参数的匹配。由于主模板中的默认参数,Functor<Foo>实际上意味着Functor<Foo, void>. 当编译器尝试使用特化来实例化它时,它会尝试将两个参数与特化中的参数进行匹配并且失败,因为void无法替换std::vector<int>. 然后它回退到使用主模板进行实例化。

因此,假设您所有的Vecs 都是std::vector<int>s,最快的解决方法是替换该行

template<class T, class V = void>

有了这个

template<class T, class E = std::vector<int>>

现在将使用专业化,因为参数将匹配。简单,但过于局限。显然,我们需要更好地控制特化中参数的类型,以使其与我们可以在主模板中指定为默认参数的内容相匹配。一种不需要定义新特征的快速解决方案是:

#include <iostream>
#include <vector>
#include <type_traits>

template<class T, class E = std::true_type>
struct Functor {
  void operator()() const {
    std::cerr << "general" << std::endl;
  }
};

template<class T>
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> {
  void operator()() const {
    std::cerr << "special" << std::endl;
  }
};

struct Foo {
  typedef std::vector<int> Vec;
};

int main() {
  Functor<Foo> ac;
  ac();
}

这适用于任何Vec在这里可能有意义的类型,例如,包括基本类型和数组,以及指向它们的引用或指针。

于 2014-12-04T23:38:21.357 回答
3

检测成员类型是否存在的另一种方法是使用void_t. 由于有效的部分特化比一般实现更可取,只要它们与默认参数匹配,我们想要一个在有效时评估为的类型void,并且仅在指定成员存在时才有效;这种类型通常(并且,从 C++17 开始,是规范的)称为void_t.

template<class...>
using void_t = void;

如果您的编译器不能正确支持它(在早期的 C++14 编译器中,别名模板中未使用的参数不能保证确保 SFINAE,违反上述规定void_t),可以使用解决方法。

template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

从 C++17 开始,void_t可在实用程序库中使用type_traits.

#include <iostream>
#include <vector>
#include <type_traits> // For void_t.

template<class T, class V = void>
struct Functor {
  void operator()() const {
    std::cerr << "general" << std::endl;
  }
};

// Use void_t here.
template<class T>
struct Functor<T, std::void_t<typename T::Vec>> {
  void operator()() const {
    std::cerr << "special" << std::endl;
  }
};

struct Foo {
  typedef std::vector<int> Vec;
};

int main() {
  Functor<Foo> ac;
  ac();
}

有了这个,输出special就像预期的那样。


在这种情况下,由于我们要检查成员类型是否存在,所以过程非常简单;它可以在没有表达式 SFINAE 或type_traits库的情况下完成,允许我们在必要时重写检查以使用 C++03 工具。

// void_t:
// Place above Functor's definition.
template<typename T> struct void_t { typedef void type; };

// ...

template<class T>
struct Functor<T, typename void_t<typename T::Vec>::type> {
  void operator()() const {
    std::cerr << "special" << std::endl;
  }
};

据我所知,这应该适用于大多数(如果不是全部)支持 SFINAE 的 C++03、C++11、C++14 或 C++1z 兼容的编译器。在处理稍微落后于标准的编译器时,或者在为还没有 C++11 兼容编译器的平台进行编译时,这可能很有用。


有关 的详细信息void_t,请参阅cppreference

于 2016-11-27T20:39:25.193 回答