Give Visual C++ a Switch to Standard Conformance

Andrew Pardoe

This post was written by Gabriel Dos Reis, Phil Christensen, and Andrew Pardoe

The Visual C++ Team is excited to announce that the compiler in Visual Studio 2017 RC will feature a mode much closer to ISO C++ standards conformance than any time in its history. This represents a major milestone in our journey to full ISO C++ conformance.  The mode is available now as an opt-in via the /permissive- switch but will one day become the default mode for the Visual C++ compiler.

Got a minute? Try Visual C++ conformance mode

The Visual C++ Team is previewing a compiler mode whereby longstanding non-conforming C++ constructs are rejected.  This includes fixes to pre-C++11 non-conformance bugs that affect a significant amount of existing code.  Examples are as simple as

typedef int default;  // error: ‘default’ is a keyword, not permitted here

or as advanced as the following.  Consider:

template<typename T>
struct B {
    int f();
};

template<typename T>
struct D : B<T> {
    int g();
};

template<typename T>
int D<T>::g() {
    return f();  // error: should be ‘this->f()’
}

In the definition of D::g, the symbol f is from the dependent base class B but standard C++ does not permit examining dependent base classes when looking for declarations that satisfy the use of f. That is an error in the source code that Visual C++ has long failed to diagnose. The fix is to prefix the call with this->. This fix works in both non-conformance and conformance modes.

The issue in this code sample might sound like a “two-phase name lookup” problem, but it is not quite that. In fact, two phase name lookup is the big missing piece from the first iteration of this standard conformance mode.  Visual C++ does not support two-phase name lookup in VS 2017 RC but we expect to complete it early next year. When we do, the definition of /permissive- will change to include two-phase name lookup. This is an important point: /permissive- is not a completed feature in VS 2017 RC. You will have to keep your codebase clean as we complete our conformance work in Visual C++.

Opting into the conforming mode, /permissive-, during the series of VS 2017 update is a commitment to keeping your code base clean and to fixing non-conforming constructs we fix conformance issues in Visual C++. If you make your code compile with /permissive- today you may still have to make changes when VS 2017 releases, and again with updates to VS 2017 because we may have added more bug fixes for non-conforming behaviors.

The good news is this first release contains most of the big issues. With the exception of two-phase name lookup we don’t expect any major changes. See How to fix your code for a complete list of non-conforming constructs implemented under /permissive- today and how to fix them. We’ll update this list as we complete the changes.

From the Visual Studio IDE

In Visual Studio 2017 RC there is no setting in the project properties page for /permissive-. You’ll need to enter the switch manually under “Configuration -> C/C++ -> Additional Options”.

permissive_settings

When compiling with /permissive-, the IDE compiler disables non-conforming Visual C++-specific behaviors and interprets your code following the C++ standard. You’ll notice that these language conformance features are now reflected in the areas of IDE such as IntelliSense and browsing features. In this example, setting /permissive- causes IntelliSense to flag default as an illegal identifier.

permissive1

There are currently a few places where the IntelliSense in the IDE will conform better to the C++ standard than the Visual C++ compiler.  For example, when relying on two-phase name lookup during overload resolution the compiler will emit an error.  IntelliSense, however, will show that the code is conforming and will not give any red squiggles in the editor.  You can see that happening in the screenshot below:

permissive2

Why is that? You may know that Visual C++ uses a separate compiler for IDE productivity features. Since around 2009 we’ve used a compiler front-end from the Edison Design Group (EDG) to drive IntelliSense and many other features. EDG does a fantastic job at emulating other compilers–if you use Clang in Visual Studio (either through Clang/C2 or  when targeting Android or iOS) we put EDG into “Clang mode” where it emulates Clang’s bugs and vendor-specific behaviors. Likewise, when using Visual C++, we put EDG into “Visual C++” mode.

While EDG emulates most VC++ features and bugs faithfully, there still might be some gaps due to the implementation being separate or how we integrated their compiler in the IDE.  For example, we’ve implemented some features such as ignoring names from dependent base classes, but we haven’t yet implemented others, such as two-phase name lookup (“two-phase overload resolution” on this page) that are both implemented in EDG. Thus EDG shows the “complete” results IntelliSense even though the Visual C++ compiler doesn’t compile the code correctly.

Any differences you see in the IDE and the compiler are temporary. As we complete our conformance work the IDE and the compiler behaviors will match on all code.

Is there any relation to /Za?

The compiler switch /Za was an effort started decades ago to carve out a strictly portable behavior across several C++ compilers. The effort was stalled and we no longer recommend it for new projects. The switch /Za does not support certain key Microsoft SDK header files. By contrast /permissive- offers a useful conformance mode where input C++ code is interpreted according to ISO C++ rules but also allows conforming extensions necessary to compile C++ on targets supported by Visual C++. For example, you can use /permissive- with C++/CLI. The compiler switch /Za rejects a few non-conforming constructs; however the compiler switch /permissive- is our recommendation going forward for conforming code.

The road ahead

We know that conformance changes can be impactful to your code. In order to provide the best experience we’re splitting the development of /permissive- into three stages: the development stage, the adoption stage, on-by-default.

Development stage

The initial preview of /permissive- allows you to try out the switch and make most of the changes that will be required in your code. At this stage there will be some inconsistencies in the experience: the feature set under /permissive- will grow slightly, and editor features like IntelliSense may not match exactly. But all the changes you make to your code in this stage will work with and without /permissive-.

Adoption stage

The Visual C++ team will continue to add new conformance behavior under the /permissive- option throughout the Visual Studio 2017 release cycle. But we know that you rely on libraries and that those libraries also need to work with /permissive- in order for you to opt-in. Our first priority is to ensure that all public headers from our team, and those from Microsoft as a whole, compile cleanly with the /permissive- option. For example, the standard library headers compile correctly both with /permissive- and without. We are also working with the community to make changes to existing open source C++ projects (this typically involves removal of Visual C++-specific workarounds.)

On by default

When developers have had time to migrate their code to conform more closely to the C++ standards we will flip the default so that the compiler applies full ISO C++ standard rules when interpreting your source code. This won’t happen immediately–it’s a long-term goal for our conformance work. Until we make this switch—and at least for the entire Visual Studio 2017 cycle–you will have to opt-in to /permissive-. When we do switch it on by default your existing projects won’t change, but VS will make new projects opt-in to conformance.

Call to action

We encourage you to try the new option /permissive- in your projects. Please add this option to your build settings–either in the IDE or in your build scripts. If your code is already used cross-platform (i.e., it compiles with multiple compilers on many platforms) and does not contain Visual C++ specific workarounds, the chances are that the code will compile just fine with /permissive-!

If you do use Visual C++ specific, non-conforming constructs in your code, chances are that your code won’t compile the first time with /permissive-. There’s a list of patterns below of non-conforming C++ code, the typical error message/number you’ll see, and how to fix the code.  The good news is that the fixes work both in the conforming mode and in the permissive mode. Therefore, if you try to compile with /permissive- and you correct issues, the corrected code will be good for the permissive mode too. This enables you to migrate your code to /permissive- incrementally, rather than making all of the changes at once.

How to fix your code

Here’s a list of behaviors that are affected by the permissive option in VS 2017. Note that this list is incomplete because the /permissive- switch is incomplete. As we add new conforming behaviors the potential breaking changes protected by /permissive- will expand. This means even if you fix your code to work with /permissive- today you may still have to make fixes in the future as we make Visual C++ conform better to the standard. Again, these changes will only affect your code if you opt-in by using the /permissive- switch.

Alternative tokens for logical operators (and, or, etc…)

    //Can't use reserved names for variables:
    // This includes the following:
    // and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq
    static int and = 0; // Change name (possibly to 'and_')

Warn on non-standard return type of main()

//warning C4326: return type of 'main' should be 'int' instead of 'void'
void main()
{
}

Do not allow constructors to be the typedef-ed type

 
struct A {
typedef A C;
C() {} // change to A() to fix
};

Do not allow mixing string and character constants in character array initialization.

 
char a[] = {'a', 'b', "cde" }; // change to  'c', 'd', 'e' or use a string literal

Do not allow pointer types in case statement

#define MEMORY_1 (volatile int*)(0x1234)
#define MEMORY_2 (volatile int*)(0x1235)
 
int func(int a)
{
    switch(a)
    {
     case MEMORY_1: //error C2052: 'volatile int *': illegal type for case expression
    case MEMORY_2:
        return a;
     default:
        return 0;
    }
}

 

Do not allow skipping an initializer with a goto

    void func(bool b)
    {
        if(b) goto END;
 
        int value = 0; //error C2362: initialization of 'value' is skipped by 'goto END'
              int array[10]; //Okay, not initialized.
 
        //... value used here
 
    END:
        return;
    }
 
// Fix 1: Limit the scope of value by adding {}:
 
    {
      int value = 0;
      //... value used here
    }
    END:
 
// Fix 2: Initialize/declare value before the 'goto'
 
    int value = 0;
    if(b) goto END;
    //... value used here
    END:
 
// Fix 3: Don't initialize value in the variable declaration.
 
    int value; // Okay to skip, initialization is on a different line
    value = 0;
 
    //... value used here
 
    END:

Do not treat copy initialization as direct initialization

//Example 1
#include <iostream>
struct B;
struct A { 
  operator B();
};
 
struct B { 
  B() { }
  B(A const&) { std::cout << "<direct> "; }
};
 
A::operator B() { std::cout << "<copy> "; return B(); }
 
int main() { 
  A a;
  B b1(a);  // 1) direct initialization
  B b2 = a; // 2) copy initialization under /permissive-
}
//output by default
//<direct> <direct> 
//
//output under /permissive-
//direct> <copy> 
//
//Example 2
class C
{
public:
    C() { i = 10; j = 20; }
    C(int ival) {i = ival; j = 10;}
    int i,j;
};
 
struct A
{
    int i;
    operator int() {return 11;}
};
 
struct S
{ C c; };
 
int main()
{
    S s = { A() }; // error C2440: 'initializing': cannot convert from 'A' to 'C'
                   // replace A() with static_cast<int>(A())
    return 0;
}
 
//Example 3
struct Y;
struct X {
    X();
    X(Y&);
};
 
struct Y {
    operator X&();
};
 
int main() {
    Y y;
    X x = y; // this is ambiguous because both ctors and UDCs are considered
      // change to direct initialization: X x(y);
      // or explicitly cast y to X.
}

Require unscoped enums to have an underlying type when pre-declared.

// The following line emits:error C3432: 'E': a forward declaration of an unscoped enumeration must have an underlying type
enum E; // Fix by changing to 'enum E : int' 
 
void Foo(E val);
 
enum E // This should also have ': int' added to match the above declaration.
       // This is not currently enforced by MSVC, but it is by other compilers.
{ A, B, C };

Binding a non-const reference to a temporary

 
struct S{};
 
// If arg is in 'in' parameter, then it should be made const.
void func(S& arg){}
 
int main() {
  //error C2664: 'void func(S &)': cannot convert argument 1 from 'S' to 'S &'
  //note: A non-const reference may only be bound to an lvalue
  func( S() );
  
  //Work around this by creating a local, and using it to call the function
  S s;
  func( s );
}

Lookup members in dependent base

template <class T> struct B {
  void f();
};
template <class T> struct D
    : public B<T> // B is a dependent base because its type depends on the type of T.
{
    // One possible fix is to uncomment the following line.  If this were a type don't forget the 'typename' keyword
    // using B<T>::f;
 
void g() {
      f(); // error C3861: 'f': identifier not found
           // change it to 'this->f();'
    }
};
template <class T> struct C
    : public B<T>
{
    C()
      : B() // error C2955: 'B': use of class template requires template argument list
            // Change to B<T>() to fix
    {}
};
 
void h()
{
   D<int> d;
   d.g();
   C<int> c;
}

Use of qualified names in member declarations

struct A {
    void A::f() { } // error C4596: illegal qualified name in member declaration
                    // remove redundant 'A::' to fix
};

Initializing multiple union members in a member initializer

union U
{
  U() 
    : i(1), j(1) // error C3442: Initializing multiple members of union: 'U::i' and 'U::j'
                 // Remove all but one of the initializations
  {}
  int i;
  int j;
};

Hidden friend name lookup rules

// Example 1
struct S {
friend void f(S *);
};
// Uncomment this declaration to make the hidden friend visible
//void f(S *); // this declaration makes the hidden friend visible
 
using type = void (*)(S *);
type p = &f; //error C2065: 'f': undeclared identifier

/*--------------------------------------------------------*/

// Example 2
struct S {
friend void f(S *);
};
 
void g() { 
    // using nullptr instead of S prevents argument dependent lookup in S
    f(nullptr); // error C3861: 'f': identifier not found
    
S *p = nullptr;
f(p); // hidden friend can be found via argument-dependent lookup.
} 

Using scoped enums in array bounds

enum class Color 
{ 
    Red, Green, Blue 
};
 
int data[Color::Blue];// error C3411: 'Color' is not valid as the size of an array as it is not an integer type
                      // Cast to type size_t or int

Using ‘default’ as an identifier in native code

void func(int default); // Error C2321: 'default' is a keyword, and cannot be used in this context

‘for each’ in native code

void func()
{
   int array[] = {1, 2, 30, 40};
   for each( int i in array) // error C4496: nonstandard extension 'for each' used: replace with ranged-for statement
                             // for( int i: array)
   {
       // ... 
   }
}

Defaulting conformance switches

The compiler switches /Zc:strictStrings and /Zc:rvalueCast are currently off by default, allowing non-conforming behavior. The switch /permissive- turns them on by default. You can pass in the /Zc flags after /permissive- to override this behavior if needed.

See these MSDN pages for more information:

  • /Zc:strictStrings https://msdn.microsoft.com/en-us/library/dn449508.aspx
  • /Zc:rvalueCast https://msdn.microsoft.com/en-us/library/dn449507.aspx

ATL attributes

We started to deprecate attributed ATL support in VS 2008. The /permissive- switch removes support for attributed ATL.

// Example 1
[uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")]
class A {};
// Fix for example 1
class __declspec(uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")) B {};
// Example 2
[emitidl];
[module(name="Foo")];
 
[object, local, uuid("9e66a290-4365-11d2-a997-00c04fa37ddb")]
__interface ICustom {
HRESULT Custom([in] long l, [out, retval] long *pLong);
[local] HRESULT CustomLocal([in] long l, [out, retval] long *pLong);
};
 
[coclass, appobject, uuid("9e66a294-4365-11d2-a997-00c04fa37ddb")]
class CFoo : public ICustom
{};
// Fix for example 2
// First, create the *.idl file. The vc140.idl generated file can be used to automatically obtain *.idl file for the interfaces with annotation. 
// Second, add a midl step to your build system to make sure that the C++ interface definitions are outputted. 
// Lastly, adjust your existing code to use ATL directly as shown in atl implementation section

-- IDL  FILE -- 
import "docobj.idl";
 
[object, local, uuid(9e66a290-4365-11d2-a997-00c04fa37ddb)] 
interface ICustom : IUnknown {
HRESULT  Custom([in] long l, [out,retval] long *pLong);
[local] HRESULT  CustomLocal([in] long l, [out,retval] long *pLong);
};
 
[ version(1.0), uuid(29079a2c-5f3f-3325-99a1-3ec9c40988bb) ]
library Foo {
importlib("stdole2.tlb");
importlib("olepro32.dll");
 
[version(1.0), appobject, uuid(9e66a294-4365-11d2-a997-00c04fa37ddb)] 
 
coclass CFoo { interface ICustom; };
}
 
-- ATL IMPLEMENTATION--
#include <idl.header.h>
#include <atlbase.h>
 
class ATL_NO_VTABLE CFooImpl : public ICustom, public ATL::CComObjectRootEx<CComMultiThreadModel> {
public:
BEGIN_COM_MAP(CFooImpl)
COM_INTERFACE_ENTRY(ICustom)
END_COM_MAP()
}; 

Changes not present in VS 2017 RTM

Here are some changes to /permissive- that we expect to enable in future releases and updates of VS 2017. This list may not be complete.

  • Two-phase name lookup
  • Error when binding a non-const reference to a temporary
  • Not treating copy init as direct init
  • Not allowing multiple user-defined conversions in initialization.
  • Alternative tokens for logical operators (‘and’, ‘or’, etc…)

Some changes have been made in the compiler and will be included in a future release of VS 2017. If you’re using unreleased compiler drops such as daily MSVC builds from NuGet, you will see these changes.

Type safety of scoped enumerations in switch statements (in a future VS release)

One reason scoped enumeration are preferred are because they have stricter type safety then unscoped enumerations. We were breaking that type safety in switch statements, which would allow you to accidently mix enumeration types. This can result in runtime behavior that was unexpected by the developer.

This also applies to pointer conversions in a switch statement.

enum class A
{
  a1,
  a2
};
enum class B
{
  baz,
  foo,
  a2
};

int f(A val) {
  switch (val)
  {
    case B::baz: // If this conversion was intended, use a static_cast
    // case static_cast<A>(B::baz):  
      return 1; 
    case B::a2: // This case may use the incorrect type, change to the intended type
    // case A::a2:
      return 2;
  }
  return 0;
}

In closing

As always, we welcome your feedback. Please give us feedback about the /permissive- feature in the comments below or through e-mail at visualcpp@microsoft.com.

If you encounter other problems with Visual C++ in VS 2017 RC please let us know via the Report a Problem option, either from the installer or the Visual Studio IDE itself. For suggestions, let us know through UserVoice. Thank you!

0 comments

Discussion is closed.

Feedback usabilla icon