类模板构造函数中的 SFINAE

Posted

技术标签:

【中文标题】类模板构造函数中的 SFINAE【英文标题】:SFINAE in class template constructors 【发布时间】:2013-04-30 13:13:33 【问题描述】:

我正在尝试使用模板和 SFINAE 制作一些东西,我是其中的初学者。我正在浪费大量时间来完成每件最简单的事情。你能帮我理解它是如何工作的吗?

C 的构造函数接受一个 T 参数,它可以是 A 或 B,但在这两种情况下具有不同的行为。我无法向你展示我试图这样做的一切。这是我认为最不愚蠢的方式。

template<typename T> class A
public: A() ;

template<typename T> class B
public: B() ;

template<typename T> struct enable_if_A         ;
template<typename T> struct enable_if_A< A<T> > typedef A<T> type;;

template<typename T> struct enable_if_B         ;
template<typename T> struct enable_if_B< B<T> > typedef B<T> type;;

template<typename T,typename... Ts> class C
public:
    C(typename enable_if_A<T>::type const &p)cout << "A" << endl;
    C(typename enable_if_B<T>::type const &p)cout << "B" << endl;
;

// ...

A<float> a;
B<float> b;

C<A<float> > ca(a); // error: no type named ‘type’ in ‘struct enable_if_B<A<float> >'
C<B<float> > cb(b); // error: no type named ‘type’ in ‘struct enable_if_A<B<float> >'

注意:我使用的是 g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1。我应该升级它吗?

谢谢

编辑:有关更多详细信息,我也尝试过(除其他外):

template<typename T,typename... Ts> class C
public:
    template<>
    C(typename enable_if_A<T>::type const &p)cout << "A" << endl;
    template<>
    C(typename enable_if_B<T>::type const &p)cout << "B" << endl;
;
//explicit specialization in non-namespace scope ‘class bcifs::C<T, Ts>’

//////////////////////////////////////////////////////////////////////////

template<typename T,typename... Ts> class C
public:
    template<typename E=void>
    C(typename enable_if_A<T>::type const &p)cout << "A" << endl;
    template<typename E=void>
    C(typename enable_if_B<T>::type const &p)cout << "B" << endl;
;
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >'

//////////////////////////////////////////////////////////////////////////

template<typename T> struct enable_if_A         ;
template<typename T> struct enable_if_A< A<T> > typedef void type;;

template<typename T> struct enable_if_B         ;
template<typename T> struct enable_if_B< B<T> > typedef void type;;

template<typename T,typename... Ts> class C
public:
    template<typename E=void>
    C(T const &p);

    C<typename enable_if_A<T>::type>(T const &p)cout << "A" << endl;
    C<typename enable_if_B<T>::type>(T const &p)cout << "B" << endl;
;
// error: invalid declarator before ‘(’ token

//////////////////////////////////////////////////////////////////////////

template<typename T> class C
public:
    template<>
    C(T const &p,typename enable_if_A<T>::type * = 0)cout << "A" << endl;
    template<>
    C(T const &p,typename enable_if_B<T>::type * = 0)cout << "B" << endl;
;
// error: explicit specialization in non-namespace scope ‘class C<T>’
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >’

//////////////////////////////////////////////////////////////////////////

template<typename T> class C
public:
    template<typename U>
    C(T const &p,typename enable_if_A<T>::type * = 0)cout << "A" << endl;
    template<typename U>
    C(T const &p,typename enable_if_B<T>::type * = 0)cout << "B" << endl;
;
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >’
// error: no matching function for call to ‘C<A<float> >::C(A<float>&)’

//////////////////////////////////////////////////////////////////////////

template<typename T> struct enable_if_A         ;
template<typename T> struct enable_if_A< A<T> > typedef void type;;

template<typename T> struct enable_if_B         ;
template<typename T> struct enable_if_B< B<T> > typedef void type;;

template<typename T> class C
public:
    template <typename U>
    C(A<U> const & r, void* _ = 0);
;

template <typename T>
template <typename U>
C<T>::C<T>(A<U> const & r, typename enable_if_A<U>::type* _ = 0) 
    cout << "A" << endl;

// error: ISO C++ forbids declaration of ‘C’ with no type [-fpermissive]
// error: function template partial specialization ‘C<T>’ is not allowed
// error: no ‘int C<T>::C(const A<U>&, typename enable_if_A<U>::type*)’ member function declared in class ‘C<T>’
// C<T>::C<U>(... does the same

很抱歉,我从未设法运行您的解决方案。我终于找到了:

// dummy-function-parameter-ed version :

template<typename T> class C
public:
    template <typename U>
    C(A<U> const &r,typename enable_if<is_same<A<U>,T>::value>::type* = 0)cout << "A" << endl;

    template <typename U>
    C(B<U> const &r,typename enable_if<is_same<B<U>,T>::value>::type* = 0)cout << "B" << endl;
;

// and the dummy-template-parameter-ed version :

template<typename T> class C
public:
    template<typename U,typename E = typename enable_if<is_same<A<U>,T>::value>::type>
    C(A<U> &r)cout << "A" << endl;

    template<typename U,typename E = typename enable_if<is_same<B<U>,T>::value>::type>
    C(B<U> &r)cout << "B" << endl;
;

【问题讨论】:

SFINAE 基于函数模板重载。也就是说,您需要有重载的函数模板(可能失败的必须是模板),而不仅仅是重载的函数。如果非模板函数包含无效类型,则您的程序格式错误。如果该函数是函数模板,则应用特殊的 SFINAE 规则并选择重载机制中的下一个可行函数。 @DyP:不一定是function模板,也可以应用于class模板,但必须应用于template 直接,而不是模板的任何成员。 @DavidRodríguez-dribeas 那会选择(部分)专业化?我认为我从未见过将 SFINAE 应用于类模板而不是函数模板.. @DyP:查看boost enable_if 文档中的启用类专业化部分。它有几个例子,其中 enable if 用于丢弃一些特化以支持其他特化,所有这些都应用于类型,而不是函数。虽然它们是专业化,但它 SFINAE,而不是基于部分排序的简单好选择 @DavidRodríguez-dribeas +1 不错。虽然它基于部分排序(+精确匹配)的选择,甚至与函数重载决议密切相关(相同的部分排序)[temp.class.order] 【参考方案1】:
template<typename T,typename... Ts> class C
public:
    C(typename enable_if_A<T>::type const &p)cout << "A" << endl;
    C(typename enable_if_B<T>::type const &p)cout << "B" << endl;
;

这是错误的,但您已经知道了 :) 原因是 SFINAE 只能应用于模板级别,但您正试图将其应用于模板的成员。也就是说,你上面模板中的SFINAE只能应用于不同的C&lt;T&gt;类型,而不能应用于C&lt;T&gt;的构造函数。

为了能够将 SFINAE 应用于构造函数,您需要将构造函数设为模板。但在您的情况下,这将导致另一个限制。构造函数是不能提供模板参数的特殊函数(即使构造函数是模板化的),这意味着必须从调用位置推导出模板类型。但是嵌套类型是不可推演的……

您可以通过更改构造函数的签名来解决此限制:

template <typename T>
template <typename U>
C<T>::C<U>(A<U> const & r, typename enable_if_A<U>::type* _ = 0) 
    // ...

在这种情况下,C 类是一个模板,它有一个模板化构造函数,该构造函数采用 A&lt;U&gt;,它只能用于 enable_if_A&lt;U&gt;::type 确实是一个类型的类型。类型可以通过第一个参数在调用的地方推导出来,推导出的类型U将被第二个参数替换。如果替换失败,模板化的构造函数将被丢弃。

上述解决方案与 C++03 兼容。如果你有一个 C++11 编译器,你可以在不需要构造函数的额外参数的情况下执行相同的操作(即不添加额外参数;如果我的语法正确,则不是 100% :)):

template <typename T>
template <typename U, typename _ = typename enable_if_A<U>::type>
C<T>::C<U>(U const &) ...

【讨论】:

我想我必须在类模板声明之外编写这个函数定义。但我仍然做不到(我把我的尝试之一放在我的帖子末尾。对不起,我尝试了很长时间,以至于我什么都不懂了。我有错误:ISO C++ 禁止声明' C' with no type [-fpermissive] // 错误:不允许函数模板部分特化 'C' // 错误:没有 'int C::C(const A&, typename enable_if_A ::type*)' 类'C'中声明的成员函数 @Gilles:模板类定义的内部或外部没有区别,重要的是,如果你想要 SFINAE,那么构造函数本身必须是一个模板。我使用了类外语法以避免在每个 sn-p 中重写包装类 我仍在尝试,但我仍然无法...您知道我会做错什么以得到消息吗:错误:函数模板部分专业化 'C' 是不允许? 好的,我找到了一种方法(见上文),但这与您的建议有点不同,我无法工作,我仍然不明白为什么。无论如何,非常感谢您花时间帮助我。 @Gilles:我需要重新审视我猜想的答案。是的,SFINAE 仅适用于模板级别直接,不适用于成员或其他方面。在类模板内部,用于实例化类的类型T 是固定的,因此没有替换失败,因此没有SFinae。关于 binding 函数参数到成员模板参数,它与任何其他类型没有什么不同,所以是的,您可以像 is_same&lt;T2,int&gt; 一样执行 is_same&lt;T1,T2&gt; 或应用任何与封闭模板的参数相关或不相关的其他元函数。【参考方案2】:

如果您不坚持使用 SFINAE,您可以通过使用具有部分特化的间接构造函数来轻松解决此问题,如下所示:

#include <iostream>

using namespace std;

template <typename T>
struct C

 T val;
 C()  init(); ;
 void init() ;
;

template<> void C<string>::init()  val = "STR"; 
template<> void C<int>::init()  val = 5; 

int main () 
  C<int> x;
  C<string> y;

  cout << y.val << endl << x.val << endl;

此示例使用 int 和 string 而不是您的类型 A 和 B,但这应该没有区别(除了它使该示例可编译)。

【讨论】:

以上是关于类模板构造函数中的 SFINAE的主要内容,如果未能解决你的问题,请参考以下文章

具有模板化类成员函数的 SFINAE

模板类构造函数中的动态分配

在派生类中使用模板基类模板构造函数

模板类中的构造函数继承 (C++11)

非模板类的模板构造函数出现问题[重复]

模板默认初始化,如果类中没有默认构造函数