Partial Support for Expression SFINAE in VS 2015 Update 1

Ulzii Luvsanbat [MSFT]

In Visual Studio 2015 Update 1, we added partial support for C++11 core language feature Expression SFINAE.

What is SFINAE?

SFINAE is an acronym for ‘Substitution Failure Is Not An Error’. The idea is that when the compiler tries to specialize a function template during overload resolution, it is ok if the specialization fails as long as there are other valid candidates. C++11 introduced features like decltype and constexpr, and it is more common to have expressions during the template argument deduction and substitution process. The C++ standards committee clarified the SFINAE rules for expressions in C++11.

Why partial implementation of Expression SFINAE?

It’s partial for a couple of reasons:

  1. We are progressively improving the parser for the Visual C++ compiler with an eye towards complete conformance for the Expressions SFINAE feature. For some insights into this work, see Rejuvenating the Microsoft C/C++ Compiler by Jim Springfield.
  2. In Visual Studio 2015 RTM, we don’t implement SFINAE rules for expressions and have limited support for Expression SFINAE. Improvements to Expression SFINAE will start with Visual Studio 2015 Update 1 and will incrementally become conformant to the C++11 Standard.

What did we do?

We are still using our old token stream-based approach and are not yet using a Recursive Descent Parse tree, so there are still some unsupported scenarios. For this release, we fixed the following issues:

Thanks to everyone who provided feedback!

Supported Scenarios in Update 1

There are four supported scenarios.

Using dependent expression in default argument of template type parameter of function template or type of function parameter:

#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);

}

Using dependent decltype in the default argument of a template type parameter of a class template:

#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”); 

Using dependent decltype in the type of a template non-type argument:

#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”); 

Using dependent decltype in the template argument of a partial specialization of a class template:

#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”);

Unsupported Scenarios in Update 1

There are currently 6 unsupported scenarios. Some have workarounds.

Declaring two functions with the same signature except for different expressions in the decltype. You will encounter this issue if you try to build the Boost library. Because we capture expressions as a token stream, we are not able to reliably compare different expressions (for example, one problem is that we don’t know what ‘T’ or ‘I’ means). All dependent decltypes are currently considered as the same type.

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

Using different decltypes as the template argument of the specializations of the same template. Similar to the issue above, you will encounter this issue if you try to build the Boost library because we are not able to distinguish between different decltypes and treat the specializations as the same. One possible workaround is to add an additional unique template argument.

template<typenameT> T declval();


template<typenameT>

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;

}

Using dependent constexpr function for SFINAE. Our current infrastructure always parses constexpr immediately whether it is dependent or not. You will encounter this issue if you try to build the range-v3 library.

 #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);

Using pack expansion inside decltype. This will be fixed in 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, typenameArgs>

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;

}

Dereferencing pointer-to-data-member inside decltype. This will be fixed in 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”);

Using non-dependent decltype in the template argument of a partial specialization of a class template. Our compiler currently can’t tell whether an expression (which is captured as a token stream) is dependent or not, so it uses heuristic which fails to identify the expression used in the bug as non-dependent. For more details, see this issue on Connect.

Plan moving forward

Most of the limitations are related to our token stream-based approach, so we are moving to a new Recursive Descent Parser-based parse tree to capture expressions. This will let us represent expressions more accurately and help us support more scenarios including Expression SFINAE usage in the Boost libraries.

We will also implement the remaining semantic requirements for Expression SFINAE including identifying whether an expression is dependent or not, enabling the comparison of dependent expression and enabling the substitution of dependent expressions.

The Visual C++ team appreciates your comments and feedback. Thank you!

Xiang Fan
Visual C++ Team

 

Normal 0

false false false

EN-US ZH-CN X-NONE

0 comments

Discussion is closed.

Feedback usabilla icon