/permissive-(标准符合性)

指定编译器的标准符合性模式。 使用此选项可帮助你识别并修复代码中的符合性问题,使其更正确且更易移植。

语法

/permissive-
/permissive

备注

Visual Studio 2017 及更高版本支持 /permissive- 选项。 Visual Studio 2019 版本 16.8 及更高版本支持 /permissive

可以使用 /permissive- 编译器选项来指定符合标准的编译器行为。 此选项禁用宽松行为,并设置 /Zc 编译器选项以执行严格的符合性。 在 IDE 中,此选项还使 IntelliSense 引擎用下划线标出不符合标准的代码。

/permissive- 选项使用当前编译器版本中的符合性支持来确定哪些语言构造不符合要求。 此选项不能确定代码是否符合特定版本的 C++ 标准。 若要为最新的草稿标准启用所有实现的编译器支持,请使用 /std:c++latest 选项。 若要将编译器支持限制为当前实现的 C++20 标准,请使用 /std:c++20 选项。 若要将编译器支持限制为当前实现的 C++17 标准,请使用 /std:c++17 选项。 若要将编译器支持限制为以更接近 C++14 标准,请使用 /std:c++14 选项(这是默认选项)。

从 Visual Studio 2019 版本 16.8 开始,/permissive- 选项由 /std:c++latest 选项隐式设置,在版本 16.11 中,由 /std:c++20 选项隐式设置。 若要支持 C++20 模块,/permissive- 是必需的。 也许你的代码不需要模块支持,但需要在 /std:c++20/std:c++latest 下启用其他功能。 可以使用不带尾部斜杠的 /permissive 选项显式启用 Microsoft 扩展支持。 /permissive 选项必须位于隐式设置 /permissive- 的任何选项之后。

默认情况下,在 Visual Studio 2017 版本 15.5 及更高版本创建的新项目中设置 /permissive- 选项。 在早期版本中,默认不设置它。 设置选项后,编译器会在代码中检测到非标准语言构造时生成诊断错误或警告。 这些构造包括 C++11 之前的代码中的一些常见 bug。

从 Windows Fall Creators SDK (10.0.16299.0) 开始,/permissive- 选项与最新 Windows 工具包(例如软件开发工具包 (SDK) 或 Windows 驱动程序工具包 (WDK))中几乎所有的头文件兼容。 由于各种源代码符合性原因,旧版 SDK 可能无法在 /permissive- 下进行编译。 编译器和 SDK 的发布时间不同,因此存在一些遗留问题。 有关具体的头文件问题,请参阅下面的 Windows 标头问题

/permissive- 选项将 /Zc:referenceBinding/Zc:strictStrings/Zc:rvalueCast 选项设置为符合行为。 这些选项默认是不符合标准的行为。 可以在命令行上将特定的 /Zc 选项传递到 /permissive- 之后以替代此行为。

在从 Visual Studio 2017 版本 15.3 开始的编译器版本中,/permissive- 选项设置了 /Zc:ternary 选项。 编译器还实现了更多关于两阶段名称查找的要求。 设置 /permissive- 选项后,编译器将分析函数和类模板定义,并标识模板中使用的依赖名称和非依赖名称。 在此版本中,仅执行名称依赖项分析。

自 Visual Studio 2022 Update 17.6 起,/permissive- 选项设置 /Zc:lambda/Zc:externConstexpr 选项。 在以前的版本中,/permissive- 未设置任何一个。

特定于环境的扩展和语言区域(标准由实现确定)不受 /permissive- 影响。 /permissive- 模式下的编译器不会标记特定于 Microsoft 的 __declspec、调用约定和结构化异常处理关键字,以及特定于编译器的 pragma 指令或属性。

早期版本的 Visual Studio 2017 中的 MSVC 编译器不支持所有符合 C++11、C++14 或 C++17 标准的代码。 根据 Visual Studio 的版本,/permissive- 选项可能不会检测到两阶段名称查找的某些方面的问题,例如将非常量引用绑定到临时、将复制初始化视为直接初始化、允许初始化中的多个用户定义的转换,或者逻辑运算符的替代令牌和其他不支持的符合性区域。 有关 Visual C++ 中一致性问题的详细信息,请参阅 Nonstandard Behavior。 为了充分利用 /permissive-,请将 Visual Studio 更新到最新版本。

如何修复代码

下面是在使用 /permissive- 时检测为不符合标准的代码的一些示例,以及对于修复问题的建议方法。

在本机代码中使用 default 作为标识符

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

在从属基中查找成员

template <typename T>
struct B
{
    void f() {}
    template <typename U>
    struct S { void operator()(){ return; } };
};

template <typename T>
struct D : public B<T> // B is a dependent base because its type
                       // depends on the type of T.
{
    // One possible fix for non-template members and function
    // template members is a using statement:
    // using B<T>::f;
    // If it's a type, don't forget the 'typename' keyword.

    void g()
    {
        f(); // error C3861: 'f': identifier not found
        // Another fix is to change the call to 'this->f();'
    }

    void h()
    {
        S<int> s; // C2065 or C3878
        // Since template S is dependent, the type must be qualified
        // with the `typename` keyword.
        // To fix, replace the declaration of s with:
        // typename B<T>::template S<int> s;
        // Or, use this:
        // typename D::template S<int> s;
        s();
    }
};

void h() {
    D<int> d;
    d.g();
    d.h();
}

在成员声明中使用限定名称

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

在成员初始值设定项中初始化多个联合成员

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 to fix.
    {}
    int i;
    int j;
};

隐藏友元名称查找规则

类外部的声明可以使隐藏的友元可见:

// 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.

使用文本 nullptr 可以防止参数依赖性查找:

// 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 now found via argument-dependent lookup.
}

可以使用 /Zc:hiddenFriend 独立于 /permissive 启用隐藏的友元名称查找规则。 如果需要让隐藏的友元名称查找具有旧式行为,但除此之外还需要有 /permissive- 行为,请使用 /Zc:hiddenFriend- 选项。

在数组边界中使用限定范围的枚举

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 to fix.

在本机代码中对每个代码使用

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)
    {
        // ...
    }
}

使用 ATL 属性

Microsoft 专用 ATL 属性在 /permissive- 下会导致问题:

// Example 1
[uuid("594382D9-44B0-461A-8DE3-E06A3E73C5EB")]
class A {};

可以改用 __declspec 表单来解决此问题:

// 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] longl, [out, retval] long*pLong);
    [local] HRESULT CustomLocal([in] longl, [out, retval] long*pLong);
};

[coclass, appobject, uuid("9e66a294-4365-11d2-a997-00c04fa37ddb")]
class CFoo : public ICustom
{};

解决方法需要额外的生成步骤。 在本例中,创建一个 IDL 文件:

// Fix for example 2
// First, create the *.idl file. The vc140.idl generated file can be
// used to automatically obtain a *.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.
// Last, adjust your existing code to use ATL directly as shown in
// the atl implementation section.

-- IDL  FILE--
import "docobj.idl";

[object, local, uuid(9e66a290-4365-11d2-a997-00c04fa37ddb)]
interface ICustom : IUnknown {
    HRESULT Custom([in] longl, [out,retval] long*pLong);
    [local] HRESULT CustomLocal([in] longl, [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()
};

不明确的条件运算符参数

在 Visual Studio 2017 版本 15.3 之前的编译器版本中,编译器接受条件运算符(或三元运算符)?: 的一些参数,这些参数被标准视为是不明确的。 在 /permissive- 模式下,编译器现在会对早期版本中编译时没有诊断的情况发出一个或多个诊断。

此更改可能导致的常见错误包括:

  • error C2593: 'operator ?' is ambiguous

  • error C2679: binary '?': no operator found which takes a right-hand operand of type 'B' (or there is no acceptable conversion)

  • error C2678: binary '?': no operator found which takes a left-hand operand of type 'A' (or there is no acceptable conversion)

  • error C2446: ':': no conversion from 'B' to 'A'

导致此问题的一个典型代码模式是,一些类 C 同时提供了来自另一种类型 T 的非显式构造函数和到类型 T 的非显式转换运算符。 将第二个参数转换为第三个参数的类型是有效的转换。 第三个参数到第二个参数类型的转换也是有效的。 由于两者都有效,因此根据标准,它是不明确的。

// Example 1: class that provides conversion to and initialization from some type T
struct A
{
    A(int);
    operator int() const;
};

extern bool cond;

A a(42);
// Accepted when /Zc:ternary or /permissive- is not used:
auto x = cond ? 7 : a; // A: permissive behavior prefers A(7) over (int)a
// Accepted always:
auto y = cond ? 7 : int(a);
auto z = cond ? A(7) : a;

当 T 表示以 null 结尾的字符串类型之一(例如,const char *const char16_t * 等)且 ?: 的实际参数是相应类型的字符串文本时,此常见模式存在一个重要的异常。 C++17 的语义与 C++14 相比有所改变。 因此,使用 /Zc:ternary/permissive- 时,示例 2 中的代码在 /std:c++14 下被接受,在 /std:c++17 或更高版本下被拒绝。

// Example 2: exception from the above
struct MyString
{
    MyString(const char* s = "") noexcept;  // from char*
    operator const char* () const noexcept; //   to char*
};

extern bool cond;

MyString s;
// Using /std:c++14, /permissive- or /Zc:ternary behavior
// is to prefer MyString("A") over (const char*)s
// but under /std:c++17 this line causes error C2445:
auto x = cond ? "A" : s;
// You can use a static_cast to resolve the ambiguity:
auto y = cond ? "A" : static_cast<const char*>(s);

可能还会在具有类型 void 的一个参数的条件运算符中看到错误。 这种情况在类似 ASSERT 的宏中可能很常见。

// Example 3: void arguments
void myassert(const char* text, const char* file, int line);
// Accepted when /Zc:ternary or /permissive- is not used:
#define ASSERT_A(ex) (void)((ex) ? 1 : myassert(#ex, __FILE__, __LINE__))
// Accepted always:
#define ASSERT_B(ex) (void)((ex) ? void() : myassert(#ex, __FILE__, __LINE__))

可能还会在模板元编程中看到错误,条件运算符结果类型可能会在 /Zc:ternary/permissive- 下更改。 解决此问题的一种方法是对生成的类型使用 std::remove_reference

// Example 4: different result types
extern bool cond;
extern int count;
char  a = 'A';
const char  b = 'B';
decltype(auto) x = cond ? a : b; // char without, const char& with /Zc:ternary
const char (&z)[2] = count > 3 ? "A" : "B"; // const char* without /Zc:ternary

两阶段名称查找

设置 /permissive- 选项后,编译器将分析函数和类模板定义,标识两阶段名称查找所需的模板中使用的依赖名称和非依赖名称。 在 Visual Studio 2017 版本 15.3 中,执行名称依赖项分析。 特别是,未在模板定义上下文中声明的非依赖名称会导致 ISO C++ 标准要求的诊断消息。 在 Visual Studio 2017 版本 15.7 中,还会绑定需要定义上下文中参数相关的查找的非依赖名称。

// dependent base
struct B {
    void g() {}
};

template<typename T>
struct D : T {
    void f() {
        // The call to g was incorrectly allowed in VS2017:
        g();  // Now under /permissive-: C3861
        // Possible fixes:
        // this->g();
        // T::g();
    }
};

int main()
{
    D<B> d;
    d.f();
}

如果需要两阶段查找的旧式行为,但除此之外还需要 /permissive- 行为,请添加 /Zc:twoPhase- 选项。

Windows 标头问题

对于 Windows Fall Creators 更新 SDK (10.0.16299.0) 或 Windows 驱动程序工具包 (WDK) 版本 1709 之前的 Windows 工具包版本,/permissive- 选项过于严格。 建议更新到最新版本的 Windows 工具包,以在 Windows 或设备驱动程序代码中使用 /permissive-

Windows 2018 年 4 月更新 SDK (10.0.17134.0)、Windows Fall Creators 更新 SDK (10.0.16299.0) 或 Windows 驱动程序工具包 (WDK) 1709 中的某些头文件仍存在问题,使其与使用 /permissive- 不兼容。 要解决这些问题,建议将这些标头的使用限制为仅需要它们的源代码文件,并在编译这些特定源代码文件时删除 /permissive- 选项。

Windows 2018 年 4 月更新 SDK (10.0.17134.0) 中发布的这些 WinRT WRL 标头因具有 /permissive- 而不干净。 要解决这些问题,要么不使用 /permissive-,要么在处理这些标头时将 /permissive-/Zc:twoPhase- 结合使用:

  • winrt/wrl/async.h 中的问题

    C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\wrl\async.h(483): error C3861: 'TraceDelegateAssigned': identifier not found
    C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\wrl\async.h(491): error C3861: 'CheckValidStateForDelegateCall': identifier not found
    C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\wrl\async.h(509): error C3861: 'TraceProgressNotificationStart': identifier not found
    C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\wrl\async.h(513): error C3861: 'TraceProgressNotificationComplete': identifier not found
    
  • winrt/wrl/implements.h 中的问题

    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\winrt\wrl\implements.h(2086): error C2039: 'SetStrongReference': is not a member of 'Microsoft::WRL::Details::WeakReferenceImpl'
    

Windows 2018 年 4 月更新 SDK (10.0.17134.0) 中发布的这些用户模式标头因具有 /permissive- 而不干净。 要解决这些问题,在处理这些标头时,请不要使用 /permissive-

  • um/Tune.h 中的问题

    C:\ProgramFiles(x86)\Windows Kits\10\include\10.0.17134.0\um\tune.h(139): error C3861: 'Release': identifier not found
    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\tune.h(559): error C3861: 'Release': identifier not found
    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\tune.h(1240): error C3861: 'Release': identifier not found
    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\tune.h(1240): note: 'Release': function declaration must be available as none of the arguments depend on a template parameter
    
  • um/spddkhlp.h 中的问题

    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\spddkhlp.h(759): error C3861: 'pNode': identifier not found
    
  • um/refptrco.h 中的问题

    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\refptrco.h(179): error C2760: syntax error: unexpected token 'identifier', expected 'type specifier'
    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\refptrco.h(342): error C2760: syntax error: unexpected token 'identifier', expected 'type specifier'
    C:\Program Files (x86)\Windows Kits\10\include\10.0.17134.0\um\refptrco.h(395): error C2760: syntax error: unexpected token 'identifier', expected 'type specifier'
    

以下问题特定于 Windows Fall Creators 更新 SDK (10.0.16299.0) 中的用户模式标头:

  • um/Query.h 中的问题

    使用 /permissive- 编译器开关时,由于 case(RTOr) 成员 ortagRESTRICTION 结构不会进行编译。

    struct tagRESTRICTION
    {
         ULONG rt;
         ULONG weight;
         /* [switch_is][switch_type] */ union _URes
         {
             /* [case()] */ NODERESTRICTION ar;
             /* [case()] */ NODERESTRICTION or;  // error C2059: syntax error: '||'
             /* [case()] */ NODERESTRICTION pxr;
             /* [case()] */ VECTORRESTRICTION vr;
             /* [case()] */ NOTRESTRICTION nr;
             /* [case()] */ CONTENTRESTRICTION cr;
             /* [case()] */ NATLANGUAGERESTRICTION nlr;
             /* [case()] */ PROPERTYRESTRICTION pr;
             /* [default] */  /* Empty union arm */
         } res;
    };
    

    要解决此问题,在编译包含 Query.h 的文件时,请不要使用 /permissive- 选项。

  • um/cellularapi_oem.h 中的问题

    使用 /permissive- 编译器开关时,前向声明 enum UICCDATASTOREACCESSMODE 会导致一条警告:

    typedef enum UICCDATASTOREACCESSMODE UICCDATASTOREACCESSMODE; // C4471
    

    未限定范围的 enum 的前向声明是 Microsoft 的一个扩展。 要解决此问题,在编译包含 cellularapi_oem.h 的文件时,请不要使用 /permissive- 选项,或者使用 /wd 选项来禁止提示警告 C4471。

  • um/omscript.h 中的问题

    在 C++03 中,从字符串字面量转换为 BSTR(即对 wchar_t * 的 typedef)已弃用,但允许这样做。 在 C++11 中,不再允许转换。

    virtual /* [id] */ HRESULT STDMETHODCALLTYPE setExpression(
         /* [in] */ __RPC__in BSTR propname,
         /* [in] */ __RPC__in BSTR expression,
         /* [in][defaultvalue] */ __RPC__in BSTR language = L"") = 0; // C2440
    

    要解决此问题,在编译包含 omscript.h 的文件时,请不要使用 /permissive- 选项,或者改用 /Zc:strictStrings-

在 Visual Studio 开发环境中设置此编译器选项

在 Visual Studio 2017 版本 15.5 及更高版本中,使用以下过程:

  1. 打开项目的“属性页”对话框。

  2. 选择“配置属性”>“C/C++”>“语言”属性页。

  3. 将“符合性模式”属性值更改为“是(/permissive-)”。 选择“确定”或“应用”以保存更改。

在 Visual Studio 2017 版本 15.5 之前的版本中,使用以下过程:

  1. 打开项目的“属性页”对话框。

  2. 选择“配置属性”>“C/C++”>“命令行”属性页

  3. 在“其他选项”框中输入“/permissive-”编译器选项。 选择“确定”或“应用”以保存更改。

以编程方式设置此编译器选项

另请参阅

MSVC 编译器选项
MSVC 编译器命令行语法