在VS 2015 Update 1 中部分支持SFINAE表达式

在VS 2015 Update 1 中部分支持SFINAE表达式

[原文发表地址] 在VS2015 Update 1 中部分支持SFINAE表达式

[原文发表时间] 2015/12/2 5:00PM

在VS 2015 Update 1  中,我们添加了部分支持c++11 核心语言模块SFINAE 表达式。

SFINAE 是什么?

SFINAE是“Substitution Failure Is Not An Error”的缩写。在重载解析期间,当编译器尝试去特化一个函数模板的时候,如果特化失败而且还有其他有效的匹配,这也是可以的。C++ 11 引进了像decltype 和constexpr一些模块,在模板参数推论和匹配的过程中有一个表达式这将会变得更加的普遍。C++ 标准委员会规定了在C++11表达式的SFINAE 规则。

为什么SFINAE表达式目前只是部分实现?

主要原因有以下两点:

  1. 我们正在逐渐的改善Visual C++编译程序的语法解析器,这种解析器将会密切关注SFINAE表达式模块完成的一致性。对于这个工作更多的了解,您可以看Jim Springfield 编写的 更新微软C/C++ 编译器
  2. 在Visual Studio 2015 RTM中,我们并没有提供表达式的SFINAE 规则只是对于SFINAE表达式提供了一些受限的支持。从Visual Studio 2015 Update 1 开始我们将完善SFINAE表达式争取与C++ 11标准上的达成一致。

我们做了什么?

我们仍然在用基于stream的旧的标识的方法 还没有用到递归向下分析树。 因此仍然有一些不支持的场景。对于这个这次发布的版本,我们解决了下面的问题:

在此感谢提供feedback的每个人!

 

在Update 1 下支持的场景

有四种可支持的场景。

对于函数模板的模板类型参数变量或者函数参数类型的缺省变量使用因变量表达式:

#include<type_traits>

 

template <typenameT>

classA

{

public:

   explicit A(Tconst&);

   template <typenameU, typenameV = std::enable_if_t<!std::is_same<U, T>::value> >

   explicit A(Uconst&);

};

 

template <typenameT>

classB

{

public:

   enum { M = 1 };

   template <int I>

   void foo(std::enable_if_t<(I == M)>* = nullptr);

   template <int I>

   void foo(std::enable_if_t<(I != M)>* = nullptr);

 

   template <int I>

   void g() { foo<I + 1>(); } // VS2015 RTM gives error C2668: 'B<int>::foo': ambiguous call to overloaded function

};

 

void f(B<int> b)

{

   b.g<0>();

   A<int>(0);

}

 

对于类模板的模板类型参数的缺省变量使用decltype类型的因变量

#include<type_traits>

 

template <classC, typenameT = decltype(&C::m)>

structM

{

   typedefTtype;

};

 

structfail_type {};

template<typenameT> typenameM<T>::type test(void *);

template<typenameT> fail_type test(...);

 

structS1 { int m; };

structS2 { };

 

static_assert(!std::is_same<decltype(test<S1>(nullptr)), fail_type>::value, "fail"); // fail in VS2015 RTM

static_assert(std::is_same<decltype(test<S2>(nullptr)), fail_type>::value, "fail");

 

对于一个模板的no-type 变量使用decltype类型的因变量

#include<type_traits>

using namespace std;

 

template <typename T, typename enable_if<is_void<decltype(declval<T>().f())>::value, int>::type V = 0>

char f(T); // VS2015 RTM can't compile this declaration

short f(...);

 

struct S1 { void f(); };

struct S2 { int f(); };

struct S3 { };

 

static_assert(sizeof(f(S1{})) == sizeof(char), "fail");

static_assert(sizeof(f(S2{})) == sizeof(short), "fail");

static_assert(sizeof(f(S3{})) == sizeof(short), "fail");

 

对于部分特化的类模板参数变量使用decltype类型的因变量

#include<type_traits>

 

template <class T, class V = void> struct is_complete : std::false_type {};

template <class T> struct is_complete<T, decltype(void(sizeof(T)))> : std::true_type {};

 

struct S1 {};

struct S2;

 

static_assert(is_complete<S1>::value, "fail"); // fail in VS2015 RTM

static_assert(!is_complete<S2>::value, "fail");

 

在Update 1中不支持的场景

当前有六种不支持的场景。 其中一些有相应变通的方法

除了在decltype中有不同的表达式之外, 用一个相同的函数签名声明了两个函数。

如果你尝试着去构建一个boost库,你将会遇到这种问题。因为我们会将一个表达式看作一个流标记来捕获,因此不可能很准确的比较出这两个表达式的不同(比如:一个问题是我们不知道“T”和“I”代表什么含义)。所有decltypes类型的因变量当前都被看作同一种类型。

template<typename I> auto foo(I i) -> decltype(i.a) {}

template<typename I> auto foo(I i) -> decltype(i.b) {} // function template has already been defined

 

相同的模板在特化的过程使用不同的decltype作为模板参数。 与上面的问题相似 如果你尝试着去构建一个boost库,你将会遇到这种问题。因为我们还不能区分出来不同的decltype 从而会将这种特化看作是相同的。一种很可能的变通方法是添加一个额外的unique模板参数.

template<typenameT> T declval();

template<typename... T>

struct void_ { typedef void type; };

 

template<typename, typename = void> struct trait {};

template<typename R>

struct trait<R(), typename void_<decltype(declval<R>()())>::type>

{

       typedef decltype(declval<R>()()) type;

};

 

template<typename R, typename T0>

struct trait<R(T0), typename void_<decltype(declval<R>()(declval<T0>()))>::type>

{

       typedef decltype(declval<R>()(declval<T0>())) type;

};

 

structS1 {

       void operator()() const;

};

 

structS2 {

       void operator()(int) const;

};

 

void f()

{

       // In VS2015 RTM, both fail to compile.

       // In VS2015 Update 1, the second still fails to compile.

       // This is because 'void_<decltype(declval<R>()(declval<T0>()))'

       // is considered the same as 'void_<decltype(declval<R>()())>', and the second partial

       // specialization uses the latter to specialize and fail.

       typedef trait<const S1()>::type type;

       typedef trait<const S2(int)>::type type;

}

 

对于SFINAE使用常量表达式函数作为因变量。

我们当前的框架是可以直接解析常量表达式的,不管它是否是因变量。如果你尝试着去构建一个range-v3库,你将会遇到这种问题。

 

 #include<type_traits>

 

template <typename T>

bool constexpr concept_fn()

{

       return std::is_same<T, int>::value;

}

template <typename T>

void f(std::enable_if_t<concept_fn<T>()>* = nullptr);

template <typenameT>

void f(std::enable_if_t<!concept_fn<T>()>* = nullptr);

 

decltype里面使用扩充, 这个将会在VS2015 Update 2 中进行修正。

template<typename T> T declval();

 

template<typename T>

struct void_ { typedef void type; };

 

template<typename, typename = void> struct trait {};

 

template<typename R, typename... Args>

struct trait<R(Args...), typenamevoid_<decltype(declval<R>()(declval<Args>()...))>::type>

{

       typedef decltype(declval<R>()(declval<Args>()...)) type;

};

 

struct S {

       void operator()(int, int) const;

};

 

void f()

{

       // fail in VS2015 Update 1

       typedef trait<constS(int, int)>::type type;

}

 

在decltype 里面解引用指向数据成员的指针。 这个将会在VS2015 Update 2 中处理

template <typename> struct AlwaysVoid {

       typedef void type;

};

 

template <typename Pmd, typename Obj, typename = void> struct IsCallableObj {

       static constexpr bool value = false;

};

template <typename Pmd, typename Obj> struct IsCallableObj<Pmd, Obj,

       typename AlwaysVoid<decltype(Obj{}.*Pmd{})>::type> {

       static constexpr bool value = true;

};

 

struct X { };

using PMD = int X::*;

class Inaccessible : privateX { };

struct Derived1 : X { };

struct Derived2 : X { };

struct Ambiguous : Derived1, Derived2 { };

 

static_assert(IsCallableObj<PMD, X>::value, "BOOM: Inaccessible");

// The following two static_asserts fail in VS2015 Update 1

static_assert(!IsCallableObj<PMD, Inaccessible>::value, "BOOM: Inaccessible");

static_assert(!IsCallableObj<PMD, Ambiguous>::value, "BOOM: Ambiguous");

 

对于部分特化的类模板变量使用了自变量decltype 我们当下的编译器还不能告诉我们一个表达式(只是被作为一个符号流来捕获)是否是用作因变量。 因此它使用是一个启发式的方式,这种方法还不能鉴别bug中使用的表达式作为一个自变量。更多详细的信息,请看issue

 

未来计划的变动

这些绝大多数限制是跟我们基于流符号方式有关。 因此我们正准备用一个新的递归向下分析树来获取表达式。这种方式将会允许我们更加准确地阐明表达式并且帮助我们支持更多的场景当然也包含在Boost 库中使用的SFINAE 表达式。我们也将会完善SFINAE 表达式剩余的场景要求。这也包含鉴别这个表达式是否是自变量,从而可以比较因变量表达式和匹配因变量表达式的不同。

 

Visual C++ 团队非常感激您的评论和反馈。 谢谢!

 

XiangFan

Visual C++ Team