SFINAE:编译器不选择专门的模板类

时间:2012-07-04 00:13:45

标签: c++ templates sfinae

我遇到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();
}

如何修复它以便自动使用专用结构?注意我不想直接对Functor上的Foo结构进行专门化,但我希望将其专门用于Vec类型的所有类型。

P.S。:我正在使用g ++ 4.4.4

3 个答案:

答案 0 :(得分:11)

很抱歉在最后一个回答中误导了你,我想了一会儿它会更简单。所以我会尝试在这里提供一个完整的解决方案。解决此类问题的一般方法是编写 traits 帮助程序模板,并将其与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的所有类型,第一个重载是更好的匹配(省略号是任何类型的最差匹配)。对于那些没有嵌套Vec的类型,SFINAE将丢弃该重载,剩下的唯一选项将是省略号。所以现在我们有一个特性来询问是否有任何类型具有嵌套的Vec类型。

启用

您可以在任何库中使用它,或者您可以自己动手,这很简单:

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

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

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

<强>实施

现在我们需要提供仅使用嵌套type的那些类型的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类型,因此特殊化将在SFINAE中被丢弃,并且将使用基本模板。

您的具体案例

在您的特定情况下,您似乎并不需要专门设置整个类型而只需要操作符,您可以将这三个元素混合为一个元素:type调度到其中一个元素基于Functor的存在的两个内部模板化函数,无需Vec和特征类:

enable_if

答案 1 :(得分:4)

即使这是一个老问题,我认为仍然值得为快速修复原始代码提供更多选择。

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

因此,假设所有Vecstd::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类型,例如基本类型和数组,以及它们的引用或指针。

答案 2 :(得分:3)

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

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

如果您的编译器没有正确支持它(在早期的C ++ 14编译器中,别名模板中未使用的参数不能保证确保SFINAE,打破上述void_t),可以使用变通方法。< / p>

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