Visual Studio 2017 中的 C++ 一致性改善、行為變更和 Bug 修正

Visual Studio 中的 Microsoft C/C++ (MSVC) 在每個版本中進行一致性改善和錯誤修正。 本文依主要版次和版本的順序列出列出改善。 若要直接跳至特定版本的變更,請使用 本文下方的清單。

本檔列出 Visual Studio 2017 中的變更。 如需 Visual Studio 2022 中變更的指南,請參閱 Visual Studio 2022 中的 C++ 一致性改善。 如需 Visual Studio 2019 變更的指南,請參閱 Visual Studio 2019 中的 C++ 一致性改善。 如需先前一致性改善的完整清單,請參閱 Visual C++ 新功能 2003 到 2015

Visual Studio 2017 RTW (15.0 版) 中的一致性改善

針對匯總支援一般化 constexpr 和非靜態資料成員初始化 (NSDMI) ,Visual Studio 2017 中的 MSVC 編譯器現在已針對 C++14 標準中新增的功能完成。 不過,編譯器仍缺乏一些來自 C++11 和 C++98 標準的功能。 如需編譯器的目前狀態,請參閱 Microsoft C/C++ 語言一致性

C++11:更多程式庫中的運算式 SFINAE 支援

編譯器會繼續改善其對運算式 SFINAE 的支援。 範本引數推算和替代需要,其中 decltypeconstexpr 運算式可能會顯示為範本參數。 如需詳細資訊,請參閱 Expression SFINAE improvements in Visual Studio 2017 RC (Visual Studio 2017 RC 中的運算式 SFINAE 增強功能)。

C++14:匯總的 NSDMI

匯總是陣列或具有的類別:沒有使用者提供的建構函式、沒有私用或受保護的非靜態資料成員、沒有基類,也沒有虛擬函式。 從 C++14 開始,匯總可能包含成員初始化運算式。 如需詳細資訊,請參閱 Member initializers and aggregates (成員初始設定式和彙總)。

C++14:擴充 constexpr

宣告為 的 constexpr 運算式現在允許包含特定種類的宣告,如果 和 switch 語句、迴圈語句,以及物件的存留期開始于 constexpr 運算式評估內,則為 。 不再需要 constexpr 非靜態成員函式必須是隱含 const 的 。 如需詳細資訊,請參閱 放寬函式的條件 constexpr 約束

C++17:Terse static_assert

static_assert 的訊息參數是選擇性的。 如需詳細資訊,請參閱 N3928:擴充 static_assert v2

C++17:[[fallthrough]] 屬性

/std:c++17 mode 和更新版本中, [[fallthrough]] 屬性可用於 switch 語句的內容中,做為編譯器預期執行遞補行為的提示。 此屬性可避免編譯器發出警告。 如需詳細資訊,請參閱P0188R0 - Wording for [[fallthrough]] attribute

一般化範圍型 for 迴圈

範圍型 for 迴圈不再需要 , begin()end() 傳回相同類型的物件。 這項變更可讓您 end() 傳回 sentinel,如 中的 range-v3 範圍和已完成但尚未發行的範圍技術規格一樣。 如需詳細資訊,請參閱P0184R0 - Generalizing the Range-Based for Loop

Copy-list-initialization

Visual Studio 2017 使用初始化運算式清單正確地引發與物件建立相關的編譯器錯誤。 Visual Studio 2015 中未攔截到這些錯誤,而且可能會導致當機或未定義的執行時間行為。 N4594 13.3.1.7p1根據 ,在 中 copy-list-initialization ,編譯器必須考慮用於多載解析的明確建構函式。 不過,如果選擇該特定多載,它必須引發錯誤。

下列兩個範例是在 Visual Studio 2015 中編譯,但無法在 Visual Studio 2017 中編譯。

struct A
{
    explicit A(int) {}
    A(double) {}
};

int main()
{
    A a1 = { 1 }; // error C3445: copy-list-initialization of 'A' cannot use an explicit constructor
    const A& a2 = { 1 }; // error C2440: 'initializing': cannot convert from 'int' to 'const A &'

}

若要更正錯誤,請使用直接初始化︰

A a1{ 1 };
const A& a2{ 1 };

在 Visual Studio 2015 中,編譯器會以與一般複製初始化相同的方式錯誤地處理 copy-list-initialization:它考慮只轉換多載解析的建構函式。 在下列範例中,Visual Studio 2015 選擇 MyInt(23) 。 Visual Studio 2017 正確地引發錯誤。

// From http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1228
struct MyStore {
    explicit MyStore(int initialCapacity);
};

struct MyInt {
    MyInt(int i);
};

struct Printer {
    void operator()(MyStore const& s);
    void operator()(MyInt const& i);
};

void f() {
    Printer p;
    p({ 23 }); // C3066: there are multiple ways that an object
        // of this type can be called with these arguments
}

這個範例與上一個範例類似,但會引發不同的錯誤。 它會在 Visual Studio 2015 中成功,並在 Visual Studio 2017 中使用 C2668失敗。

struct A {
    explicit A(int) {}
};

struct B {
    B(int) {}
};

void f(const A&) {}
void f(const B&) {}

int main()
{
    f({ 1 }); // error C2668: 'f': ambiguous call to overloaded function
}

被取代的 typedef

Visual Studio 2017 現在會針對類別或結構中宣告的已淘汰 typedefs 發出正確的警告。 下列範例會在 Visual Studio 2015 中編譯但不顯示警告。 它會在 Visual Studio 2017 中產生 C4996

struct A
{
    // also for __declspec(deprecated)
    [[deprecated]] typedef int inttype;
};

int main()
{
    A::inttype a = 0; // C4996 'A::inttype': was declared deprecated
}

constexpr

當條件式評估作業的左側運算元在內容中 constexpr 無效時,Visual Studio 2017 會正確引發錯誤。 下列程式碼會在 Visual Studio 2015 中編譯,但不在 Visual Studio 2017 中編譯,其中會引發 C3615

template<int N>
struct array
{
    int size() const { return N; }
};

constexpr bool f(const array<1> &arr)
{
    return arr.size() == 10 || arr.size() == 11; // C3615 constexpr function 'f' cannot result in a constant expression
}

若要更正錯誤,請將 array::size() 函式宣告為 constexpr,或從 f 中移除 constexpr 限定詞。

傳遞給 variadic 函式的類別類型

在 Visual Studio 2017 中,傳遞至 variadic printf 函式的類別或結構必須可簡單複製。 傳遞這類物件時,編譯器只會進行位複製,而且不會呼叫建構函式或解構函式。

#include <atomic>
#include <memory>
#include <stdio.h>

int main()
{
    std::atomic<int> i(0);
    printf("%i\n", i); // error C4839: non-standard use of class 'std::atomic<int>'
                        // as an argument to a variadic function.
                        // note: the constructor and destructor will not be called;
                        // a bitwise copy of the class will be passed as the argument
                        // error C2280: 'std::atomic<int>::atomic(const std::atomic<int> &)':
                        // attempting to reference a deleted function

    struct S {
        S(int i) : i(i) {}
        S(const S& other) : i(other.i) {}
        operator int() { return i; }
    private:
        int i;
    } s(0);
    printf("%i\n", s); // warning C4840 : non-portable use of class 'main::S'
                      // as an argument to a variadic function
}

若要更正錯誤,您可以呼叫會傳回可完整複製類型的成員函式,

    std::atomic<int> i(0);
    printf("%i\n", i.load());

或者先使用靜態轉型來轉換物件,再傳遞它︰

    struct S {/* as before */} s(0);
    printf("%i\n", static_cast<int>(s))

對於使用 CString 所建置和管理的字串,所提供的 operator LPCTSTR() 應該用來將 物件轉換成 CString 格式字串所預期的 C 指標。

CString str1;
CString str2 = _T("hello!");
str1.Format(_T("%s"), static_cast<LPCTSTR>(str2));

類別建構中的 CV 限定詞

在 Visual Studio 2015 中,透過建構函式呼叫來產生類別物件時,編譯器有時會錯誤地忽略 cv 限定詞。 此問題可能會導致當機或意外執行階段行為。 下列範例是在 Visual Studio 2015 中編譯,但在 Visual Studio 2017 中引發編譯器錯誤:

struct S
{
    S(int);
    operator int();
};

int i = (const S)0; // error C2440

若要更正錯誤,請將 operator int() 宣告為 const

範本中限定名稱的存取檢查

舊版編譯器並未檢查某些範本內容中限定名稱的存取權。 此問題可能會干擾預期的 SFINAE 行為,因為名稱無法存取,因此替代預期會失敗。 它可能會導致執行時間發生當機或非預期的行為,因為編譯器不正確地呼叫運算子的錯誤多載。 在 Visual Studio 2017 中,會引發編譯器錯誤。 特定錯誤可能會有所不同,但典型的錯誤是 C2672:「找不到相符的多載函式」。下列程式碼會在 Visual Studio 2015 中編譯,但在 Visual Studio 2017 中引發錯誤:

#include <type_traits>

template <class T> class S {
    typedef typename T type;
};

template <class T, std::enable_if<std::is_integral<typename S<T>::type>::value, T> * = 0>
bool f(T x);

int main()
{
    f(10); // C2672: No matching overloaded function found.
}

遺漏範本引數清單

在 Visual Studio 2015 和更早版本中,編譯器並未診斷所有遺漏的範本引數清單。 當範本參數清單中出現遺漏的範本時,它不會注意:例如,當預設範本引數的一部分或非類型範本參數遺失時。 此問題可能會導致無法預期的行為,包括編譯器當機或意外執行階段行為。 下列程式碼在 Visual Studio 2015 中會編譯,但在 Visual Studio 2017 中會產生錯誤。

template <class T> class ListNode;
template <class T> using ListNodeMember = ListNode<T> T::*;
template <class T, ListNodeMember M> class ListHead; // C2955: 'ListNodeMember': use of alias
                                                     // template requires template argument list

// correct:  template <class T, ListNodeMember<T> M> class ListHead;

Expression-SFINAE

為了支援 expression-SFINAE,編譯器現在會在宣告範本而非具現化範本時剖析 decltype 引數。 因此,如果在 引數中找到 decltype 非相依特製化,則不會延遲到具現化時間。 系統會立即處理,而且會在當下診斷出任何產生的錯誤。

下列範例顯示在宣告時引發的這類編譯器錯誤︰

#include <utility>
template <class T, class ReturnT, class... ArgsT>
class IsCallable
{
public:
    struct BadType {};

    template <class U>
    static decltype(std::declval<T>()(std::declval<ArgsT>()...)) Test(int); //C2064. Should be declval<U>

    template <class U>
    static BadType Test(...);

    static constexpr bool value = std::is_convertible<decltype(Test<T>(0)), ReturnT>::value;
};

constexpr bool test1 = IsCallable<int(), int>::value;
static_assert(test1, "PASS1");
constexpr bool test2 = !IsCallable<int*, int>::value;
static_assert(test2, "PASS2");

宣告於匿名命名空間中的類別

根據 C++ 標準,宣告於匿名命名空間中的類別具有內部連結,這表示無法將它匯出。 在 Visual Studio 2015 和更早的版本中,並未強制執行此規則。 在 Visual Studio 2017 中,會部分強制執行此規則。 在 Visual Studio 2017 中,下列範例會引發錯誤 C2201

struct __declspec(dllexport) S1 { virtual void f() {} };
  // C2201 const anonymous namespace::S1::vftable: must have external linkage
  // in order to be exported/imported.

實值類別成員的預設初始設定式 (C++/CLI)

在 Visual Studio 2015 和以前版本中,編譯器允許 (但忽略) 實值類別成員的預設成員初始設定式。 實值類別的預設初始化一律會以零初始化成員。 不允許預設建構函式。 在 Visual Studio 2017 中,預設成員初始設定式會引發編譯器錯誤,如這個範例中所示︰

value struct V
{
    int i = 0; // error C3446: 'V::i': a default member initializer
               // isn't allowed for a member of a value class
};

預設索引子 (C++/CLI)

在 Visual Studio 2015 和以前版本中,在某些情況下,編譯器會將預設屬性錯誤地識別為預設索引子。 使用 default 識別項存取屬性,即可解決問題。 在 C++11 中將 default 用作 關鍵字後,該因應措施本身會成為問題。 在 Visual Studio 2017 中,已修正需要因應措施的錯誤。 當 用來存取類別的預設屬性時 default ,編譯器現在會引發錯誤。

//class1.cs

using System.Reflection;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{
    [DefaultMember("Value")]
    public class Class1
    {
        public int Value
        {
            // using attribute on the return type triggers the compiler bug
            [return: MarshalAs(UnmanagedType.I4)]
            get;
        }
    }
    [DefaultMember("Value")]
    public class Class2
    {
        public int Value
        {
            get;
        }
    }
}

// code.cpp
#using "class1.dll"

void f(ClassLibrary1::Class1 ^r1, ClassLibrary1::Class2 ^r2)
{
       r1->Value; // error
       r1->default;
       r2->Value;
       r2->default; // error
}

在 Visual Studio 2017 中,您可以依名稱存取這兩個實值屬性︰

#using "class1.dll"

void f(ClassLibrary1::Class1 ^r1, ClassLibrary1::Class2 ^r2)
{
       r1->Value;
       r2->Value;
}

15.3 中的一致性改善

constexpr Lambda

Lambda 運算式現在可用於常數運算式。 如需詳細資訊,請參閱constexpr C++ 中的 Lambda 運算式

函式範本中的 if constexpr

函式範本可包含 if constexpr 陳述式,以進行編譯時間分支。 如需詳細資訊,請參閱if constexpr 語句

使用初始設定式的選取範圍陳述式

if 陳述式可包含會在陳述式之內於區塊範圍導入變數的初始設定式。 如需詳細資訊,請參閱if 使用初始化運算式的 語句

[[maybe_unused]][[nodiscard]] 屬性

當未使用實體時,新屬性 [[maybe_unused]] 會關閉警號。 如果捨棄函式呼叫的傳回值,[[nodiscard]] 屬性會建立警告。 如需詳細資訊,請參閱 C++ 中的屬性

不重複使用屬性命名空間

新的語法讓您在屬性清單中可只用單一命名空間識別項。 如需詳細資訊,請參閱 C++ 中的屬性

結構化繫結

現在可以在單一宣告中以值元件的個別名稱儲存值,前提是該值必須為陣列、std::tuplestd::pair,或具有公用非靜態資料成員。 如需詳細資訊,請參閱 P0144R0 - Structured Bindings從函式傳回多個值

enum class 值的建構規則

目前有一個非縮小範圍列舉的隱含轉換。 它會從範圍列舉的基礎型別轉換成列舉本身。 當其定義未導入列舉值,以及來源使用清單初始化語法時,即可使用轉換。 如需詳細資訊,請參閱 P0138R2 - Construction Rules for enum class Values列舉

以值擷取 *this

Lambda 運算式中的 *this 物件現已可以值擷取。 此變更可用在平行及非同步作業中叫用 Lambda 的案例,特別是在較新的電腦架構上。 如需詳細資訊,請參閱P0018R3 - Lambda Capture of *this by Value as [=,*this]

移除 booloperator++

bool 類型已不再支援 operator++。 如需詳細資訊,請參閱P0002R1 - Remove Deprecated operator++(bool)

移除已淘汰的 register 關鍵字

register 關鍵字先前已淘汰 (且編譯器已略過),且現已從語言移除。 如需詳細資訊,請參閱P0001R1 - Remove Deprecated Use of the register Keyword

對已刪除之成員範本的呼叫

在舊版的 Visual Studio 中,在某些情況下,編譯器無法發出錯誤來呼叫已刪除的成員範本。 這些呼叫可能會在執行時間造成當機。 下列程式碼現在會產生 C2280

template<typename T>
struct S {
   template<typename U> static int f() = delete;
};

void g()
{
   decltype(S<int>::f<int>()) i; // this should fail with
// C2280: 'int S<int>::f<int>(void)': attempting to reference a deleted function
}

若要修正錯誤,請將 i 宣告為 int

類型特性的先決條件檢查

Visual Studio 2017 15.3 版改進了類型特性的先決條件檢查,以更嚴格地遵守標準。 此類檢查是可供指派的。 下列程式碼會在 Visual Studio 2017 15.3 版中產生 C2139

struct S;
enum E;

static_assert(!__is_assignable(S, S), "fail"); // C2139 in 15.3
static_assert(__is_convertible_to(E, E), "fail"); // C2139 in 15.3

原生至 Managed 封送處理上的新編譯器警告和執行階段檢查

從受控函式對原生函式的呼叫需要封送處理。 CLR 會執行封送處理,但它並不了解 C++ 語意。 如果您根據傳遞原生物件,CLR 會呼叫物件的 copy-constructor,或使用 BitBlt,這可能在執行階段導致未定義的行為。

現在,如果編譯器在編譯時期發現這個錯誤,編譯器就會發出警告:具有已刪除複製函式的原生物件會依值在原生與 Managed 界限之間傳遞。 針對編譯器在編譯時期不知道的那些情況,其會插入執行階段檢查,以便程式會在發生語式錯誤的封送處理時,立即呼叫 std::terminate。 在 Visual Studio 2017 15.3 版中,下列程式碼會產生警告 C4606

class A
{
public:
   A() : p_(new int) {}
   ~A() { delete p_; }

   A(A const &) = delete;
   A(A &&rhs) {
   p_ = rhs.p_;
}

private:
   int *p_;
};

#pragma unmanaged

void f(A a)
{
}

#pragma managed

int main()
{
    // This call from managed to native requires marshaling. The CLR doesn't
    // understand C++ and uses BitBlt, which results in a double-free later.
    f(A()); // C4606 'A': passing argument by value across native and managed
    // boundary requires valid copy constructor. Otherwise, the runtime
    // behavior is undefined.`
}

若要修正錯誤,請移除 #pragma managed 指示詞,以將呼叫者標示為原生並避免封送處理。

WinRT 的實驗性 API 警告

針對實驗和意見反應所發行的 WinRT API 會附有 Windows.Foundation.Metadata.ExperimentalAttribute。 在 Visual Studio 2017 15.3 版中,編譯器會產生此屬性的警告 C4698 。 舊版 Windows SDK 中的一些 API 已附有該屬性,而呼叫這些 API 現在會觸發此編譯器警告。 較新的 Windows SDK 屬性已從所有出貨類型中移除。 如果您使用較舊的 SDK,則必須隱藏所有寄送類型的呼叫的這些警告。

下列程式碼會產生警告 C4698:

Windows::Storage::IApplicationDataStatics2::GetForUserAsync(); // C4698
// 'Windows::Storage::IApplicationDataStatics2::GetForUserAsync' is for
// evaluation purposes only and is subject to change or removal in future updates

若要停用該警告,請加入 #pragma:

#pragma warning(push)
#pragma warning(disable:4698)

Windows::Storage::IApplicationDataStatics2::GetForUserAsync();

#pragma warning(pop)

範本成員函式的程式碼外部定義

Visual Studio 2017 15.3 版針對未在 類別中宣告之範本成員函式的行外定義產生錯誤。 下列程式碼現在會產生錯誤 C2039

struct S {};

template <typename T>
void S::f(T t) {} // C2039: 'f': is not a member of 'S'

若要修正錯誤,請將宣告加入類別:

struct S {
    template <typename T>
    void f(T t);
};
template <typename T>
void S::f(T t) {}

嘗試取得 this 指標的位址

在 C++ 中, this 是 X 類型指標的 prvalue。您無法擷取 的 this 位址,或將其系結至左值參考。 在舊版的 Visual Studio 中,編譯器可讓您使用轉換以避開此限制。 在 Visual Studio 2017 15.3 版中,編譯器會產生錯誤 C2664

轉換成無法存取的基底類別

當您嘗試將類型轉換為無法存取的基底類別時,Visual Studio 2017 15.3 版會產生錯誤。 下列程式碼是語式錯誤的,且可能在執行階段造成當機。 當編譯器看到如下的程式碼時,編譯器現在會產生 C2243

#include <memory>

class B { };
class D : B { }; // C2243: 'type cast': conversion from 'D *' to 'B *' exists, but is inaccessible

void f()
{
   std::unique_ptr<B>(new D());
}

成員函式的程式碼外部定義不允許預設引數

範本類別中成員函式的程式碼外部定義不允許預設引數。 編譯器會在 底下 /permissive 發出警告,並在 底下 /permissive- 發出硬式錯誤。

在舊版的 Visual Studio 中,下列語式錯誤的程式碼可能會導致執行階段當機。 Visual Studio 2017 15.3 版會產生警告 C5037

template <typename T>
struct A {
    T f(T t, bool b = false);
};

template <typename T>
T A<T>::f(T t, bool b = false) // C5037: 'A<T>::f': an out-of-line definition of a member of a class template cannot have default arguments
{
    // ...
}

若要修正錯誤,請移除 = false 預設引數。

使用 offsetof 搭配複合成員指示項

在 Visual Studio 2017 15.3 版中,使用 offsetof(T, m)m 是 「複合成員指示項」時,當您使用 /Wall 選項進行編譯時,會產生警告。 下列程式碼的語式錯誤,且可能在執行階段造成當機。 Visual Studio 2017 15.3 版會產生警告 C4841

struct A {
   int arr[10];
};

// warning C4841: non-standard extension used: compound member designator used in offsetof
constexpr auto off = offsetof(A, arr[2]);

若要修正程式碼,您可以使用 pragma 停用警告,或將程式碼變更為不使用 offsetof

#pragma warning(push)
#pragma warning(disable: 4841)
constexpr auto off = offsetof(A, arr[2]);
#pragma warning(pop)

使用 offsetof 搭配靜態資料成員或成員函式

在 Visual Studio 2017 15.3 版中,使用 offsetof(T, m) (其中 m 代表靜態資料成員或成員函式) 會導致錯誤。 下列程式碼會產生錯誤 C4597

#include <cstddef>

struct A {
   int ten() { return 10; }
   static constexpr int two = 2;
};

constexpr auto off = offsetof(A, ten);  // C4597: undefined behavior: offsetof applied to member function 'A::ten'
constexpr auto off2 = offsetof(A, two); // C4597: undefined behavior: offsetof applied to static data member 'A::two'

此程式碼的語式錯誤,且可能在執行階段造成當機。 若要修正錯誤,請變更程式碼以不再叫用未定義的行為。 它是不可移植且 C++ 標準不允許的程式碼。

針對 __declspec 屬性的新警告

在 Visual Studio 2017 15.3 版中,如果 __declspec(...) 套用於 extern "C" 連結規格前,編譯器就不再略過該屬性。 編輯器之前會忽略該屬性,這可能隱含執行階段。 /Wall設定 和 /WX 選項時,下列程式碼會產生警告C4768

__declspec(noinline) extern "C" HRESULT __stdcall // C4768: __declspec attributes before linkage specification are ignored

若要修正此警告,請先加入extern "C"

extern "C" __declspec(noinline) HRESULT __stdcall

Visual Studio 2017 15.3 版預設會關閉此警告,且只會影響使用 /Wall/WX 編譯的程式碼。 從 Visual Studio 2017 15.5 版開始,預設會將其啟用為層級 3 警告。

decltype 和對已刪除之解構函式的呼叫

在舊版的 Visual Studio 中,在與 decltype 相關聯的運算式內容中呼叫已刪除的解構函式時,編譯器並不會偵測到。 在 Visual Studio 2017 15.3 版中,下列程式碼會產生錯誤 C2280

template<typename T>
struct A
{
   ~A() = delete;
};

template<typename T>
auto f() -> A<T>;

template<typename T>
auto g(T) -> decltype((f<T>()));

void h()
{
   g(42); // C2280: 'A<T>::~A(void)': attempting to reference a deleted function
}

未初始化的 const 變數

Visual Studio 2017 RTW 版本有回歸:C++ 編譯器不會針對未初始化的 const 變數發出診斷。 Visual Studio 2017 15.3 版已修正此迴歸。 下列程式碼現在會產生警告 C4132

const int Value; // C4132: 'Value': const object should be initialized

若要修正錯誤,請將值指派給 Value

空白宣告

Visual Studio 2017 15.3 版現在會針對所有類型的空白宣告發出警告,而不是只針對內建類型。 下列程式碼現在會產生所有四個宣告的層級 2 C4091 警告:

struct A {};
template <typename> struct B {};
enum C { c1, c2, c3 };

int;    // warning C4091 : '' : ignored on left of 'int' when no variable is declared
A;      // warning C4091 : '' : ignored on left of 'main::A' when no variable is declared
B<int>; // warning C4091 : '' : ignored on left of 'B<int>' when no variable is declared
C;      // warning C4091 : '' : ignored on left of 'C' when no variable is declared

若要移除警告,請將空白宣告註解化或移除。 在未命名物件可能具有副作用 (例如 RAII) 的情況下,應該提供名稱給它。

警告會排除在 底下 /Wv:18 ,且預設為在警告層級 W2 下開啟。

陣列型別的 std::is_convertible

舊版的編譯器會針對陣列類型提供不正確的結果 std::is_convertible 。 這要求程式庫作者在使用 std::is_convertible<...> 類型特性時,以特殊案例處理 Microsoft C++ 編譯器。 下例中,靜態判斷提示能夠通過舊版 Visual Studio,但在 Visual Studio 2017 15.3 版中失敗:

#include <type_traits>

using Array = char[1];

static_assert(std::is_convertible<Array, Array>::value);
static_assert(std::is_convertible<const Array, const Array>::value, "");
static_assert(std::is_convertible<Array&, Array>::value, "");
static_assert(std::is_convertible<Array, Array&>::value, "");

std::is_convertible<From, To> 的計算是查看 imaginary 函式的定義是否正確:

   To test() { return std::declval<From>(); }

私用解構函式和 std::is_constructible

舊版編譯器在決定 的結果 std::is_constructible 時,會忽略解構函式是否為私用。 現在會考慮。 下例中,靜態判斷提示能夠通過舊版 Visual Studio,但在 Visual Studio 2017 15.3 版中失敗:

#include <type_traits>

class PrivateDtor {
   PrivateDtor(int) { }
private:
   ~PrivateDtor() { }
};

// This assertion used to succeed. It now correctly fails.
static_assert(std::is_constructible<PrivateDtor, int>::value);

私人解構函式會讓類型成為不可建構的。 std::is_constructible<T, Args...> 的計算如下列宣告所示:

   T obj(std::declval<Args>()...)

這個呼叫暗示解構函式呼叫。

C2668:語意模糊的多載解析

舊版編譯器在同時透過使用宣告和引數相依查閱找到多個候選項目時,有時偵測不到語意模糊。 此失敗會導致選擇錯誤的多載和未預期的執行階段行為。 在下列範例中,Visual Studio 2017 15.3 版正確地引發 C2668

namespace N {
   template<class T>
   void f(T&, T&);

   template<class T>
   void f();
}

template<class T>
void f(T&, T&);

struct S {};
void f()
{
   using N::f;

   S s1, s2;
   f(s1, s2); // C2668: 'f': ambiguous call to overloaded function
}

若要修正程式碼,如果您想要呼叫 ::f(),請移除使用 N::f 陳述式。

C2660:區域函式宣告和引數相依查閱

區域函式宣告會將函式宣告隱藏在封閉範圍中,停用引數相依查閱。 在此案例中,舊版編譯器一律會執行引數相依查閱。 如果編譯器選擇錯誤的多載,可能會導致非預期的執行時間行為。 錯誤通常是因為區域函式宣告的簽章不正確而發生。 在下列範例中,Visual Studio 2017 15.3 版會正確地引發 C2660

struct S {};
void f(S, int);

void g()
{
   void f(S); // C2660 'f': function does not take 2 arguments:
   // or void f(S, int);
   S s;
   f(s, 0);
}

若要修正此問題,請變更或移除 f(S) 簽章。

C5038:初始設定式清單中的初始化順序

類別成員會依照宣告的順序初始化,而不是它們出現在初始化運算式清單中的順序。 當初始設定式清單的順序和宣告順序不一致時,舊版編譯器未發出警告。 如果某個成員的初始化相依于清單中已初始化的另一個成員,則此問題可能會導致未定義的執行時間行為。 在下列範例中,Visual Studio 2017 15.3 版 (,) /Wall 引發警告 C5038

struct A
{    // Initialized in reverse, y reused
    A(int a) : y(a), x(y) {} // C5038: data member 'A::y' will be initialized after data member 'A::x'
    int x;
    int y;
};

若要修正此問題,請將初始設定式清單排列成和宣告一樣的順序。 當一或兩個初始設定式參考基底類別成員時,就會引發類似的警告。

此警告預設為 off-by-default,且只會影響使用 /Wall 編譯的程式碼。

15.5 中的一致性改善

標示為 [14] 的功能可以無條件地在模式中使用 /std:c++14

extern constexpr 的新編譯器參數

在舊版的 Visual Studio 中,編譯器一律會提供 constexpr 變數內部連結,即使變數標示 extern 為 也一樣。 在 Visual Studio 2017 15.5 版中,新的編譯器參數 /Zc:externConstexpr 會啟用正確且符合標準的行為。 如需詳細資訊,請參閱extern constexpr 連結

移除動態例外狀況規格

P0003R5 動態例外狀況規格在 C++11 中已淘汰。 此功能已從 C++17 中移除,但 (仍) 已被取代的 throw() 規格嚴格保留為 的 noexcept(true) 別名。 如需詳細資訊,請參閱動態例外狀況規格移除和 noexcept

not_fn()

P0005R4not_fn是 和 not2not1 取代。

重寫 enable_shared_from_this

P0033R1enable_shared_from_this已在 C++11 中新增。 此 C++17 標準會更新規格,以更妥善處理某些極端的案例。 [14]

接合地圖與集合

P0083R3 這項功能可讓您從關聯容器擷取節點, (也就是 map 、、 setunordered_mapunordered_set) ,然後可以修改並插入回相同容器或使用相同節點類型的不同容器。 (常見的使用案例是從 std::map 擷取節點,再變更金鑰,然後重新插入。)

淘汰不必要的程式庫組件

P0174R2 C++ 標準程式庫有幾項功能多年來逐漸被較新的功能所取代,或是發現不實用或有問題。 這些功能在 C++17 中已正式被取代。

移除 std::function 中的配置器支援

P0302R1 在 C++17 之前,類別範本 std::function 有數個採用配置器引數的建構函式。 不過,在此內容中使用配置器會有問題,而且語意不清。 已移除有問題的建構函式。

not_fn() 的修正

P0358R1std::not_fn 的新寫法支援在用於包裝函式引動過程時傳播值類別。

shared_ptr<T[]>, shared_ptr<T[N]>

P0414R2 將程式庫基本概念中的 shared_ptr 變更合併到 C++17。 [14]

修正陣列的 shared_ptr

P0497R0 修正陣列的 shared_ptr 支援。 [14]

釐清 insert_return_type

P0508R0 具有唯一金鑰的關聯容器和具有唯一金鑰的未排序容器包含傳回巢狀型別 insert_return_type 的成員函式 insert。 該傳回型別現在已定義為在容器的 Iterator 和 NodeType 上參數化的類型特製化。

標準程式庫的內嵌變數

針對 P0607R0,標準程式庫中宣告的數個常見變數現在會內嵌宣告。

附錄 D 已淘汰的功能

C++ 標準的附錄 D 包含已淘汰的所有功能,包括 shared_ptr::unique()<codecvt>namespace std::tr1/std:c++17設定 或更新版本的編譯器選項時,附錄 D 中幾乎所有的標準程式庫功能都會標示為已被取代。 如需詳細資訊,請參閱 附錄 D 中的標準程式庫功能標示為已被取代

中的 std::tr2::sys<experimental/filesystem> 命名空間現在預設會在 底下 /std:c++14 發出取代警告,而且現在預設會移除 /std:c++17 和 更新版本。

藉由避免使用非標準的延伸模組 (類別內明確特製化) 來改善 <iostream> 中的一致性。

標準程式庫現在會於內部使用變數樣板。

標準程式庫已更新,以回應 C++17 編譯器變更。 更新在型別系統中加入 noexcept ,以及移除動態例外狀況規格。

部分排序變更

編譯器現在會正確地拒絕下列程式碼,並提供正確的錯誤訊息:

template<typename... T>
int f(T* ...)
{
    return 1;
}

template<typename T>
int f(const T&)
{
    return 2;
}

int main()
{
    int i = 0;
    f(&i);    // C2668
}
t161.cpp
t161.cpp(16): error C2668: 'f': ambiguous call to overloaded function
t161.cpp(8): note: could be 'int f<int*>(const T &)'
        with
        [
            T=int*
        ]
t161.cpp(2): note: or       'int f<int>(int*)'
t161.cpp(16): note: while trying to match the argument list '(int*)'

上述範例的問題在於類型中有兩個差異 (const 與非 const 以及 pack 與非 pack)。 若要排除編譯器錯誤,請移除其中一個差異。 然後,編譯器可以明確排序函式。

template<typename... T>
int f(T* ...)
{
    return 1;
}

template<typename T>
int f(T&)
{
    return 2;
}

int main()
{
    int i = 0;
    f(&i);
}

例外狀況處理常式

陣列或函式類型參考的處理常式從未可以比對所有例外狀況物件。 編譯器現在會正確接受此規則,並引發層級 4 警告 C4843。 當使用 時 /Zc:strictStrings ,它也不會再比對 或 wchar_t*char* 的處理常式與字串常值。

int main()
{
    try {
        throw "";
    }
    catch (int (&)[1]) {} // C4843 (This should always be dead code.)
    catch (void (&)()) {} // C4843 (This should always be dead code.)
    catch (char*) {} // This should not be a match under /Zc:strictStrings
}
warning C4843: 'int (&)[1]': An exception handler of reference to array or function type is unreachable, use 'int*' instead
warning C4843: 'void (__cdecl &)(void)': An exception handler of reference to array or function type is unreachable, use 'void (__cdecl*)(void)' instead

下列程式碼可避免這個錯誤:

catch (int (*)[1]) {}

std::tr1 命名空間已淘汰

非標準 std::tr1 命名空間在 C++14 和 C++17 模式中現在皆標記為已淘汰。 在 Visual Studio 2017 15.5 版中,下列程式碼會引發 C4996:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    std::tr1::function<int (int, int)> f = std::plus<int>(); //C4996
    cout << f(3, 5) << std::endl;
    f = std::multiplies<int>();
    cout << f(3, 5) << std::endl;
}
warning C4996: 'std::tr1': warning STL4002: The non-standard std::tr1 namespace and TR1-only machinery are deprecated and will be REMOVED. You can define _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING to acknowledge that you have received this warning.

若要修正此錯誤,請移除 tr1 命名空間的參考:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    std::function<int (int, int)> f = std::plus<int>();
    cout << f(3, 5) << std::endl;
    f = std::multiplies<int>();
    cout << f(3, 5) << std::endl;
}

附錄 D 中的標準程式庫功能標示為已被取代

/std:c++17設定模式或更新版本的編譯器參數時,附錄 D 中幾乎所有標準程式庫功能都會標示為已被取代。

在 Visual Studio 2017 15.5 版中,下列程式碼會引發 C4996:

#include <iterator>

class MyIter : public std::iterator<std::random_access_iterator_tag, int> {
public:
    // ... other members ...
};

#include <type_traits>

static_assert(std::is_same<MyIter::pointer, int*>::value, "BOOM");
warning C4996: 'std::iterator<std::random_access_iterator_tag,int,ptrdiff_t,_Ty*,_Ty &>::pointer': warning STL4015: The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. (The <iterator> header is NOT deprecated.) The C++ standard has never required user-defined iterators to derive from std::iterator. To fix this warning, stop deriving from std::iterator and start providing publicly accessible typedefs named iterator_category, value_type, difference_type, pointer, and reference. Note that value_type is required to be non-const, even for constant iterators. You can define _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.

若要修正錯誤,請遵循警告文字中的指示,如下列程式碼所示:

#include <iterator>

class MyIter {
public:
    typedef std::random_access_iterator_tag iterator_category;
    typedef int value_type;
    typedef ptrdiff_t difference_type;
    typedef int* pointer;
    typedef int& reference;

    // ... other members ...
};

#include <type_traits>

static_assert(std::is_same<MyIter::pointer, int*>::value, "BOOM");

未參考的區域變數

在 Visual Studio 15.5 中,會在更多情況下發出警告 C4189 ,如下列程式碼所示:

void f() {
    char s[2] = {0}; // C4189. Either use the variable or remove it.
}
warning C4189: 's': local variable is initialized but not referenced

若要修正錯誤,請移除未使用的變數。

單行註解

在 Visual Studio 2017 15.5 版中,C 編譯器不再發出警告 C4001 和 C4179。 之前,它們只會在編譯器參數下 /Za 發出。 由於自 C99 起,單行註解已成為 C 標準的一部分,因此不再需要這些警告。

/* C only */
#pragma warning(disable:4001) // C4619
#pragma warning(disable:4179)
// single line comment
//* single line comment */
warning C4619: #pragma warning: there is no warning number '4001'

當程式碼不需要回溯相容時,請移除 C4001 和 C4179 隱藏,以避免警告。 如果程式碼確實需要與舊版相容,則只隱藏 C4619。

/* C only */

#pragma warning(disable:4619)
#pragma warning(disable:4001)
#pragma warning(disable:4179)

// single line comment
/* single line comment */

包含 extern "C" 連結的 __declspec 屬性

在舊版的 Visual Studio 中,若在 extern "C" 連結規格之前套用 __declspec(...) 屬性,編譯器會忽略 __declspec(...)。 此行為會產生使用者不想要且可能隱含執行階段的程式碼。 Visual Studio 15.3 版已新增 C4768 警告,但預設為關閉。 在 Visual Studio 2017 15.5 版中,預設會啟用該警告。

__declspec(noinline) extern "C" HRESULT __stdcall // C4768
warning C4768: __declspec attributes before linkage specification are ignored

若要修正錯誤,請在 __declspec 前面加上連結規格:

extern "C" __declspec(noinline) HRESULT __stdcall

Visual Studio 2017 15.3 或舊版隨附的一些 Windows SDK 標頭 (例如:10.0.15063.0 版,也稱為 RS2 SDK) 會提供這項新警告 C4768。 不過,較新版的 Windows SDK 標頭 (特別是 ShlObj.h 和 ShlObj_core.h) 已經過修正,因此不會產生這個警告。 當您看到 Windows SDK 標頭中出現這個警告時,您可以採取下列動作:

  1. 切換至 Visual Studio 2017 15.5 版隨附的最新 Windows SDK。

  2. 關閉 Windows SDK 標頭陳述式之 #include 前後的警告:

   #pragma warning (push)
   #pragma warning(disable:4768)
   #include <shlobj.h>
   #pragma warning (pop)

extern constexpr 聯動

在舊版的 Visual Studio 中,編譯器一律會為 constexpr 變數提供內部連結,即使變數以 extern 標記也一樣。 在 Visual Studio 2017 15.5 版中,新的編譯器參數 (/Zc:externConstexpr) 啟用正確、符合標準的行為。 此行為最後終究會成為預設值。

extern constexpr int x = 10;
error LNK2005: "int const x" already defined

如果標頭檔包含宣告為 extern constexpr 的變數,就需要以 __declspec(selectany) 標記,才能正確地合併其重複宣告:

extern constexpr __declspec(selectany) int x = 10;

typeid 不可用於不完整的類別類型

在舊版的 Visual Studio 中,編譯器不正確地允許下列程式碼,因此導致可能不正確的類型資訊。 在 Visual Studio 2017 15.5 版中,編譯器會正確地引發錯誤:

#include <typeinfo>

struct S;

void f() { typeid(S); } //C2027 in 15.5
error C2027: use of undefined type 'S'

std::is_convertible 目標類型

std::is_convertible 要求目標類型必須是有效的傳回型別。 在舊版的 Visual Studio 中,編譯器不正確地允許抽象類型,因此可能導致不正確的多載解析和非預期的執行階段行為。 下列程式碼現在會正確地引發 C2338:

#include <type_traits>

struct B { virtual ~B() = 0; };
struct D : public B { virtual ~D(); };

static_assert(std::is_convertible<D, B>::value, "fail"); // C2338 in 15.5

若要避免錯誤,當使用 is_convertible 時,您應該比較指標類型,因為如果某個類型為抽象,非指標類型比較可能會失敗:

#include <type_traits>

struct B { virtual ~B() = 0; };
struct D : public B { virtual ~D(); };

static_assert(std::is_convertible<D *, B *>::value, "fail");

動態例外狀況規格移除和 noexcept

在 C++17 中,throw()noexcept 的別名、throw(<type list>)throw(...) 會遭到移除,而且某些類型可能包含 noexcept。 此變更會導致符合 C++14 或更早版本之程式碼的來源相容性。 參數 /Zc:noexceptTypes- 可用來還原為 的 C++14 版本 noexcept ,一般使用 C++17 模式。 它可讓您更新原始程式碼以符合 C++17,而不需要同時重寫所有 throw() 程式碼。

編譯器現在也會在 C++17 模式的宣告中,或使用 /permissive- 新的警告 C5043 來診斷更多不相符的例外狀況規格。

下列程式碼會在套用參數時 /std:c++17 ,在 Visual Studio 2017 15.5 版中產生 C5043 和 C5040:

void f() throw(); // equivalent to void f() noexcept;
void f() {} // warning C5043
void g() throw(); // warning C5040

struct A {
    virtual void f() throw();
};

struct B : A {
    virtual void f() { } // error C2694
};

若要在使用 /std:c++17 時移除錯誤,請將 參數新增 /Zc:noexceptTypes- 至命令列,或將程式碼更新為使用 noexcept ,如下列範例所示:

void f() noexcept;
void f() noexcept { }
void g() noexcept(false);

struct A {
    virtual void f() noexcept;
};

struct B : A {
    virtual void f() noexcept { }
};

內嵌變數

靜態 constexpr 資料成員現在是隱含 inline 的 ,這表示其類別內的宣告現在是其定義。 針對資料成員使用行外定義 static constexpr 是多餘的,現在已被取代。 在 Visual Studio 2017 15.5 版中 /std:c++17 ,當參數套用時,下列程式碼現在會產生警告 C5041:

struct X {
    static constexpr int size = 3;
};
const int X::size; // C5041: 'size': out-of-line definition for constexpr static data member is not needed and is deprecated in C++17

extern "C" __declspec(...) 警告 C4768 現在預設會開啟

在 Visual Studio 2017 15.3 版中已新增警告,但預設為關閉。 在 Visual Studio 2017 15.5 版中,預設會啟用該警告。 如需詳細資訊,請參閱 屬性的新 __declspec 警告

預設函式和 __declspec(nothrow)

當對應的基底/成員函式允許例外狀況時,編譯器之前允許使用 __declspec(nothrow) 宣告預設函式。 此行為違反 C++ 標準,而且可能在執行階段導致未定義的行為。 如果不符合例外狀況規格,此標準會要求將這類函式定義為已刪除。 在 底下 /std:c++17 ,下列程式碼會引發 C2280:

struct A {
    A& operator=(const A& other) { // No exception specification; this function may throw.
        ...
    }
};

struct B : public A {
    __declspec(nothrow) B& operator=(const B& other) = default;
};

int main()
{
    B b1, b2;
    b2 = b1; // error C2280: attempting to reference a deleted function.
             // Function was implicitly deleted because the explicit exception
             // specification is incompatible with that of the implicit declaration.
}

若要修正此程式碼,請從預設函式中移除 __declspec(nothrow) 或移除 = default,然後提供該函式的定義,以及任何必要的例外狀況處理:

struct A {
    A& operator=(const A& other) {
        // ...
    }
};

struct B : public A {
    B& operator=(const B& other) = default;
};

int main()
{
    B b1, b2;
    b2 = b1;
}

noexcept 和部分特製化

noexcept 類型系統中,比對特定「可呼叫」類型的部分特製化可能無法編譯,或無法選擇主要範本,因為指標對 noexcept-functions 缺少部分特製化。

在這種情況下,您可能需要新增更多部分特製化來處理成員函式的 noexcept 函式指標和 noexcept 指標。 這些多載只有在模式或更新版本中才合法 /std:c++17 。 如果必須維持與 C++14 舊版相容,而且您正撰寫的程式碼有其他人使用,則您應該使用 #ifdef 指示詞括住這些新的多載。 如果您是在獨立模組中工作,則您可以直接使用 /Zc:noexceptTypes- 參數進行編譯,而不是使用 #ifdef 防護。

下列程式碼會在 下 /std:c++14 編譯,但在 下 /std:c++17 失敗,並出現錯誤 C2027:

template <typename T> struct A;

template <>
struct A<void(*)()>
{
    static const bool value = true;
};

template <typename T>
bool g(T t)
{
    return A<T>::value;
}

void f() noexcept {}

int main()
{
    return g(&f) ? 0 : 1; // C2027: use of undefined type 'A<T>'
}

下列程式碼會成功, /std:c++17 因為編譯器選擇新的部分特製化 A<void (*)() noexcept>

template <typename T> struct A;

template <>
struct A<void(*)()>
{
    static const bool value = true;
};

template <>
struct A<void(*)() noexcept>
{
    static const bool value = true;
};

template <typename T>
bool g(T t)
{
    return A<T>::value;
}

void f() noexcept {}

int main()
{
    return g(&f) ? 0 : 1; // OK
}

15.6 中的一致性改善

C++17 程式庫基本概念 V1

P0220R1 將 C++17 的程式庫基本技術規格合併至標準。 涵蓋 、、、 <experimental/any><experimental/functional><experimental/memory><experimental/string_view><experimental/memory_resource> 、 和 的 <experimental/algorithm> 更新 <experimental/tuple><experimental/optional>

C++17:改善標準程式庫的類別範本引數推斷

P0739R0adopt_lock_t 移至參數清單前,使 scoped_lock 能夠一致使用 scoped_lock。 允許 std::variant 建構函式在更多案例中參與多載解析,以啟用複製指派。

15.7 中的一致性改善

C++17:重新撰寫繼承建構函式

P0136R1 指定 using 命名建構函式的宣告現在可讓衍生類別的初始化看見對應的基類建構函式,而不是宣告更多衍生類別建構函式。 此重寫是來自 C++14 的變更。 在 Visual Studio 2017 15.7 版和更新版本中,在 /std:c++17 mode 和更新版本中,在 C++14 中有效且使用繼承建構函式的程式碼可能無效,或可能有不同的語意。

下列範例顯示 C++14 行為:

struct A {
    template<typename T>
    A(T, typename T::type = 0);
    A(int);
};

struct B : A {
    using A::A;
    B(int n) = delete; // Error C2280
};

B b(42L); // Calls B<long>(long), which calls A(int)
          //  due to substitution failure in A<long>(long).

下列範例顯示 /std:c++17 Visual Studio 15.7 中的行為:

struct A {
    template<typename T>
    A(T, typename T::type = 0);
    A(int);
};

struct B : A {
    using A::A;
    B(int n)
    {
        //do something
    }
};

B b(42L); // now calls B(int)

如需詳細資訊,請參閱建構函式

C++17:擴充匯總初始化

P0017R1

如果基類的建構函式為非公用,但衍生類別可存取,則在 Visual Studio 2017 15.7 版的 /std:c++17 模式和更新版本中,您無法再使用空大括弧來初始化衍生類型的物件。 下列範例顯示 C++14 符合行為:

struct Derived;
struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
               // which can call Base ctor.

在 C++17 中,Derived 已視作彙總類型。 因此,透過私用預設建構函式將 Base 初始化會直接包含在擴充彙總初始化規則的過程。 Base 私用建構函式在先前會透過 Derived 建構函式呼叫,而成功的因素是 friend 宣告所致。 下列範例顯示 Visual Studio 15.7 版的 C++17 行為模式 /std:c++17

struct Derived;
struct Base {
    friend struct Derived;
private:
    Base() {}
};
struct Derived : Base {
    Derived() {} // add user-defined constructor
                 // to call with {} initialization
};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // error C2248: 'Base::Base': cannot access
               // private member declared in class 'Base'

C++17:使用 auto 宣告非類型範本參數

P0127R2

/std:c++17 模式中,編譯器現在可以推斷使用 auto 宣告的非類型樣板引數類型:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

這項新功能的其中一個效果是有效的 C++14 程式碼可能無效,或可能有不同的語意。 例如,部分先前無效的多載現在會變得有效。 下列範例顯示因為對 example(p) 的呼叫已繫結至 example(void*);,而編譯的 C++14 程式碼。 在 Visual Studio 2017 15.7 版的模式中 /std:c++17 ,函 example 式範本最符合。

template <int N> struct A;
template <typename T, T N> int example(A<N>*) = delete;

void example(void *);

void sample(A<0> *p)
{
    example(p); // OK in C++14
}

下列範例示範 Visual Studio 15.7 模式中的 /std:c++17 C++17 程式碼:

template <int N> struct A;
template <typename T, T N> int example(A<N>*);

void example(void *);

void sample(A<0> *p)
{
    example(p); // C2280: 'int example<int,0>(A<0>*)': attempting to reference a deleted function
}

C++17:基底字元串轉換 (部分)

P0067R5 介於整數與字串之間,以及浮點數與字串之間的低階、無關地區設定的函式轉換。

C++20:避免不必要的衰減 (部分)

P0777R1 為 "decay'' 的概念與單純移除常式或參考限定詞之間的差異新增區別。 新增類型特徵 remove_reference_t 來取代部分內容中的 decay_t。 Visual Studio 2019 中已實作 remove_cvref_t 的支援。

C++17:平行演算法

P0024R2 平行處理原則 TS 已併入標準,但有些微修改。

C++17:hypot(x, y, z)

P0030R1針對類型 floatdouble 、 和 long double ,將三個新的多載新增至 std::hypot ,每個多載都有三個輸入參數。

C++17:<filesystem>

P0218R1 為標準採用檔案系統 TS,其中包括些許用詞修改。

C++17:數學特殊函式

P0226R1 將數學特殊函式先前的技術規格採用至標準 <cmath> 標頭。

C++17:標準程式庫的推算指南

P0433R2 更新 STL 以利用 C++17 對 P0091R3 的採用,這為類別範本引數推斷新增了支援。

C++17:修復基礎字串轉換

P0682R1 將新的基底字元串轉換函式從 P0067R5 移至新的標頭 <charconv> ,並進行其他改善,包括將錯誤處理變更為使用 std::errc ,而不是 std::error_code

C++17:char_traitsconstexpr (部分)

P0426R1std::traits_type成員函 length 式 、 comparefind 的變更,可在 std::string_view 常數運算式中使用。 (Visual Studio 2017 15.6 版中,僅支援 Clang/LLVM。在 15.7 版中,ClXX 的支援幾乎已完成。)

C++17:主要類別範本中的預設引數

此行為變更是 的 P0091R3 - Template argument deduction for class templates 先決條件。

在此之前,編譯器會忽略主要類別範本中的預設引數:

template<typename T>
struct S {
    void f(int = 0);
};

template<typename T>
void S<T>::f(int = 0) {} // Re-definition necessary

/std:c++17 Visual Studio 2017 15.7 版的模式中,不會忽略預設引數:

template<typename T>
struct S {
    void f(int = 0);
};

template<typename T>
void S<T>::f(int) {} // Default argument is used

相依名稱解析

此行為變更是 的 P0091R3 - Template argument deduction for class templates 先決條件。

在下列範例中,Visual Studio 15.6 及舊版中的編譯器,在主要類別範本中會將 D::type 解析為 B<T>::type

template<typename T>
struct B {
    using type = T;
};

template<typename T>
struct D : B<T*> {
    using type = B<T*>::type;
};

在 模式中 /std:c++17 ,Visual Studio 2017 15.7 版需要 typename D 語句中的 using 關鍵字。如果沒有 typename ,編譯器會引發警告 C4346: 'B<T*>::type': dependent name is not a type 和錯誤 C2061: : syntax error: identifier 'type'

template<typename T>
struct B {
    using type = T;
};

template<typename T>
struct D : B<T*> {
    using type = typename B<T*>::type;
};

C++17:[[nodiscard]] 屬性 - 警告等級增加

在 Visual Studio 2017 15.7 版的模式中 /std:c++17C4834 的警告層級會從 W3 增加到 W1。 您可以使用轉換成 void 的 來停用警告,或傳遞 /wd:4834 至編譯器。

[[nodiscard]] int f() { return 0; }

int main() {
    f(); // warning C4834: discarding return value
         // of function with 'nodiscard'
}

Variadic 範本建構函式基底類別初始化清單

在先前的 Visual Studio 版本中,有一個缺少範本引數的 variadic 範本建構函式基底類別初始化清單錯誤地被允許而未產生錯誤。 在 Visual Studio 2017 15.7 版中,會引發編譯器錯誤。

下列 Visual Studio 2017 15.7 版的程式碼範例會引發錯誤 C2614:

template<typename T>
struct B {};

template<typename T>
struct D : B<T>
{

    template<typename ...C>
    D() : B() {} // C2614: D<int>: illegal member initialization: 'B' is not a base or member
};

D<int> d;

若要修正錯誤,請將 B() 運算式變更為 B<T>()

constexpr 彙總初始化

舊版 C++ 編譯器不正確地處理 constexpr 匯總初始化。 編譯器已接受不正確程式碼,其中 aggregate-init-list 有太多元素,並為其產生不正確的物件程式碼。 以下程式碼為此類程式碼的範例:

#include <array>
struct X {
    unsigned short a;
    unsigned char b;
};

int main() {
    constexpr std::array<X, 2> xs = { // C2078: too many initializers
        { 1, 2 },
        { 3, 4 }
    };
    return 0;
}

在 Visual Studio 2017 15.7 版 Update 3 和更新版本中,先前的範例現在會引發 C2078。 以下範例顯示如何修正該程式碼。 當使用巢狀大括弧初始化清單將 std::array 初始化時,讓內部陣列使用自己的大括弧清單:

#include <array>
struct X {
    unsigned short a;
    unsigned char b;
};

int main() {
    constexpr std::array<X, 2> xs = {{ // note double braces
        { 1, 2 },
        { 3, 4 }
    }}; // note double braces
    return 0;
}

15.8 中的一致性改善

非限定識別碼上的 typename

/permissive- 模式中,編譯器不再接受別名範本定義中不合格識別碼的假 typename 關鍵字。 下列程式碼現在會產生 C7511:

template <typename T>
using  X = typename T; // C7511: 'T': 'typename' keyword must be 
                       // followed by a qualified name

若要修正此錯誤,請將第二行變更為 using X = T;

別名範本定義右側的 __declspec()

__declspec 已不再允許在別名範本定義的右側。 先前,編譯器已接受但忽略此程式碼。 使用別名時,永遠不會產生取代警告。

可以改用標準 C++ 屬性 [[deprecated]] ,並在 Visual Studio 2017 15.6 版中遵守。 下列程式碼現在會產生 C2760:

template <typename T>
using X = __declspec(deprecated("msg")) T; // C2760: syntax error:
                                           // unexpected token '__declspec',
                                           // expected 'type specifier'`

若要修正此錯誤,請變更為下列程式碼 (使用位於別名定義 '=' 之前的屬性):

template <typename T>
using  X [[deprecated("msg")]] = T;

兩階段名稱查閱診斷

兩階段名稱查閱需要讓範本能夠在定義時看到範本主體中使用的非相依名稱。 之前,Microsoft C++ 編譯器會在具現化時期之前將找不到的名稱保持未查閱。 現在,它需要在範本主體中繫結非相依名稱。

表示這種情況的其中一個方法是使用相依基底類別的查詢。 先前,編譯器允許使用相依基類中定義的名稱。 這是因為在解析所有類型時,會在具現化期間查閱它們。 現在,該程式碼會被視為錯誤。 在這些情況下,您可以強制在具現化時期查閱變數,方法是使用基底類別類型進行限定,或以其他方式使其相依,例如新增 this-> 指標。

/permissive- 模式中,下列程式碼現在會引發 C3861:

template <class T>
struct Base {
    int base_value = 42;
};

template <class T>
struct S : Base<T> {
    int f() {
        return base_value; // C3861: 'base_value': identifier not found
    }
};

若要修正此錯誤,請將 return 陳述式變更為 return this->base_value;

注意

在 1.70 之前的 Boost.Python 程式庫版本中,有一個 MSVC 特定的因應措施適用于 中的 unwind_type.hpp 範本向前宣告。 從 /permissive- Visual Studio 2017 15.8 版 () _MSC_VER==1915 開始,MSVC 編譯器會正確執行引數相依名稱查閱 (ADL) 。 它現在與其他編譯器一致,因此不需要此因應措施防護。 若要避免錯誤 C3861: 'unwind_type': identifier not found ,請更新 Boost.Python 程式庫。

命名空間 std 中的向前宣告和定義

C++ 標準不允許使用者將向前宣告或定義新增到命名空間 std 中。 將宣告或定義新增至命名空間 std 或新增至命名空間 std 內的命名空間,現在會導致發生未定義的行為。

在未來的某個時間,Microsoft 將會移動一些標準程式庫類型定義所在的位置。 此變更會中斷將向前宣告新增至命名空間 std 的現有程式碼。 新警告 C4643 有助於識別這類來源問題。 警告會在模式中 /default 啟用,且預設為關閉。 這會影響使用 /Wall/WX 編譯的程式。

下列程式碼現在會引發 C4643:

namespace std {
    template<typename T> class vector;  // C4643: Forward declaring 'vector'
                                        // in namespace std is not permitted
                                        // by the C++ Standard`
}

若要修正錯誤,請使用 #include 指示詞,而不是正向宣告:

#include <vector>

委派給其本身的建構函式

C++ 標準建議編譯器應該在建構函式委派給其本身時發出診斷。 和 /std:c++latest 模式中的 /std:c++17 Microsoft C++ 編譯器現在會引發 C7535。

若未出現此錯誤,下列程式會進行編譯,但將產生無限迴圈:

class X {
public:
    X(int, int);
    X(int v) : X(v){} // C7535: 'X::X': delegating constructor calls itself
};

若要避免無限迴圈,請委派給不同的建構函式:

class X {
public:

    X(int, int);
    X(int v) : X(v, 0) {}
};

包含常數運算式的 offsetof

offsetof 傳統上已使用需要 的 reinterpret_cast 宏來實作。 此用法在需要常數運算式的內容中並不合法,但 Microsoft C++ 編譯器傳統上會允許此做法。 offsetof隨附于標準程式庫的宏會正確地使用編譯器內建 (__builtin_offsetof) ,但許多人已使用宏技巧來定義自己的 offsetof

在 Visual Studio 2017 15.8 版中,編譯器會限制預設模式中可以顯示這些 reinterpret_cast 運算子的區域,以協助程式碼符合標準的 C++ 行為。 在 底下 /permissive- ,條件約束更嚴格。 在需要常數運算式的地方使用 的結果 offsetof ,可能會導致發出警告 C4644 或 C2975 的程式碼。

下列程式碼會在預設和 /std:c++17 模式中引發 C4644,並以 模式引發 /permissive- C2975:

struct Data {
    int x;
};

// Common pattern of user-defined offsetof
#define MY_OFFSET(T, m) (unsigned long long)(&(((T*)nullptr)->m))

int main()

{
    switch (0) {
    case MY_OFFSET(Data, x): return 0; // C4644: usage of the
        // macro-based offsetof pattern in constant expressions
        // is non-standard; use offsetof defined in the C++
        // standard library instead
        // OR
        // C2975: invalid template argument, expected
        // compile-time constant expression

    default: return 1;
    }
}

若要修正錯誤,請使用 offsetof ,如 透過 <cstddef> 所定義:

#include <cstddef>

struct Data {
    int x;
};

int main()
{
    switch (0) {
    case offsetof(Data, x): return 0;
    default: return 1;
    }
}

基底類別的 CV 限定詞受限於套件展開

如果基底類別也受限於套件展開,舊版的 Microsoft C++ 編譯器不會偵測到基底類別具有 CV 限定詞。

在 Visual Studio 2017 15.8 版的 模式中 /permissive- ,下列程式碼會引發 C3770:

template<typename... T>
class X : public T... { };

class S { };

int main()
{
    X<const S> x; // C3770: 'const S': is not a valid base class
}

template 關鍵字和巢狀名稱規範

在模式中,編譯 /permissive- 程式現在需要在 template 相依巢狀名稱-name-specifier 之後,關鍵字在範本名稱前面。

模式中的 /permissive- 下列程式碼現在會引發 C7510

template<typename T> struct Base
{
    template<class U> void example() {}
};

template<typename T>
struct X : Base<T>
{
    void example()
    {
        Base<T>::example<int>(); // C7510: 'example': use of dependent
            // template name must be prefixed with 'template'
            // note: see reference to class template instantiation
            // 'X<T>' being compiled
    }
};

若要修正此錯誤,請將 template 關鍵字新增至 Base<T>::example<int>(); 陳述式,如下列範例所示:

template<typename T> struct Base
{
    template<class U> void example() {}
};

template<typename T>
struct X : Base<T>
{
    void example()
    {
        // Add template keyword here:
        Base<T>::template example<int>();
    }
};

15.9 中的一致性改善

運算子的從左到右評估順序 ->*[]>><<

從 C++17 開始,運算子的運算元 ->*[]>><< 必須以從左到右的順序評估。 編譯器在兩種案例下無法保證此順序:

  • 當其中一個運算元運算式是由值傳遞的物件或包含由值傳遞的物件,或

  • 使用 /clr 編譯時,其中一個運算元是 物件或陣列專案的欄位。

編譯器在無法保證從左到右的評估時會發出警告 C4866。 只有在指定或更新版本時 /std:c++17 ,編譯器才會產生這個警告,因為 C++17 中引進了這些運算子的由左至右順序需求。

若要解決這個警告,請先考慮是否需要由左至右評估運算元。 例如,當評估運算元可能會產生順序相依副作用時,可能需要它。 在許多情況下,評估運算元的順序沒有任何可觀察的效果。 若評估順序必須是從左到右,請考慮您是否能改為依 const 參考傳遞運算元。 此變更會消除下列程式碼範例中的警告:

// C4866.cpp
// compile with: /w14866 /std:c++17

class HasCopyConstructor
{
public:
    int x;

    HasCopyConstructor(int x) : x(x) {}
    HasCopyConstructor(const HasCopyConstructor& h) : x(h.x) { }
};

int operator>>(HasCopyConstructor a, HasCopyConstructor b) { return a.x >> b.x; }

// This version of operator>> does not trigger the warning:
// int operator>>(const HasCopyConstructor& a, const HasCopyConstructor& b) { return a.x >> b.x; }

int main()
{
    HasCopyConstructor a{ 1 };
    HasCopyConstructor b{ 2 };

    a>>b;        // C4866 for call to operator>>
};

成員別名範本中的識別碼

用於成員別名範本定義的識別碼必須先宣告才能使用。

在舊版的編譯器中,允許下列程式碼。 在 Visual Studio 2017 15.9 版中,在 /permissive- 模式中,編譯器會引發 C3861

template <typename... Ts>
struct A
{
  public:
    template <typename U>
    using from_template_t = decltype(from_template(A<U>{})); // C3861:
        // 'from_template': identifier not found

  private:
    template <template <typename...> typename Type, typename... Args>
    static constexpr A<Args...> from_template(A<Type<Args...>>);
};

A<>::from_template_t<A<int>> a;

若要修正此錯誤,請在 from_template_t 之前宣告 from_template

模組變更

在 Visual Studio 2017 15.9 版中,每當模組的命令列選項在模組建立與模組耗用量端之間不一致時,編譯器就會引發 C5050 。 下列範例中有兩個問題:

  • 在 (main.cpp) 的取用端,未指定此選項 /EHsc

  • C++ 版本位於 /std:c++17 建立端,以及 /std:c++14 取用端。

cl /EHsc /std:c++17 m.ixx /experimental:module
cl /experimental:module /module:reference m.ifc main.cpp /std:c++14

編譯器會針對這兩種情況引發 C5050:

warning C5050: Possible incompatible environment while
importing module 'm': mismatched C++ versions.
Current "201402" module version "201703".

每當檔案遭到竄改時 .ifc ,編譯器也會引發C7536。 模組介面的標頭下方會包含內容的 SHA2 雜湊。 匯入時,檔案 .ifc 會經過雜湊處理,然後根據標頭中提供的雜湊進行檢查。 如果這些不符合,就會引發錯誤 C7536:

error C7536: ifc failed integrity checks.
Expected SHA2: '66d5c8154df0c71d4cab7665bab4a125c7ce5cb9a401a4d8b461b706ddd771c6'

涉及別名及非推算內容的部分排序

涉及非推算內容中別名的部分排序規則裡發生實作發散。 在下列範例中,GCC 和 Microsoft C++ 編譯器在模式中 /permissive- () 引發錯誤,而 Clang 接受程式碼。

#include <utility>
using size_t = std::size_t;

template <typename T>
struct A {};
template <size_t, size_t>
struct AlignedBuffer {};
template <size_t len>
using AlignedStorage = AlignedBuffer<len, 4>;

template <class T, class Alloc>
int f(Alloc &alloc, const AlignedStorage<T::size> &buffer)
{
    return 1;
}

template <class T, class Alloc>
int f(A<Alloc> &alloc, const AlignedStorage<T::size> &buffer)
{
    return 2;
}

struct Alloc
{
    static constexpr size_t size = 10;
};

int main()
{
    A<void> a;
    AlignedStorage<Alloc::size> buf;
    if (f<Alloc>(a, buf) != 2)
    {
        return 1;
    }

    return 0;
}

上述範例會引發 C2668

partial_alias.cpp(32): error C2668: 'f': ambiguous call to overloaded function
partial_alias.cpp(18): note: could be 'int f<Alloc,void>(A<void> &,const AlignedBuffer<10,4> &)'
partial_alias.cpp(12): note: or       'int f<Alloc,A<void>>(Alloc &,const AlignedBuffer<10,4> &)'
        with
        [
            Alloc=A<void>
        ]
partial_alias.cpp(32): note: while trying to match the argument list '(A<void>, AlignedBuffer<10,4>)'

實作散發的原因是 C++ 標準寫法中的迴歸。 核心問題 2235 的解決辦法移除了某些會允許排序這些多載的文字。 目前的 C++ 標準並不提供將這些函式部分排序的機制,因此會將他們視為不明確。

建議的因應措施是,不依賴部分排序來解決此問題。 反之,請使用 SFINAE 來移除特定多載。 在下列範例中,我們使用協助程式類別 IsA,在 AllocA 的專屬表示時移除第一個多載:

#include <utility>
using size_t = std::size_t;

template <typename T>
struct A {};
template <size_t, size_t>
struct AlignedBuffer {};
template <size_t len>
using AlignedStorage = AlignedBuffer<len, 4>;

template <typename T> struct IsA : std::false_type {};
template <typename T> struct IsA<A<T>> : std::true_type {};

template <class T, class Alloc, typename = std::enable_if_t<!IsA<Alloc>::value>>
int f(Alloc &alloc, const AlignedStorage<T::size> &buffer)
{
    return 1;
}

template <class T, class Alloc>
int f(A<Alloc> &alloc, const AlignedStorage<T::size> &buffer)
{
    return 2;
}

struct Alloc
{
    static constexpr size_t size = 10;
};

int main()
{
    A<void> a;
    AlignedStorage<Alloc::size> buf;
    if (f<Alloc>(a, buf) != 2)
    {
        return 1;
    }

    return 0;
}

樣板化函式定義中不合法的運算式和非常值類型

現在,系統可正確診斷已明確特殊化之樣板化函式定義中不合法的運算式和非常值類型。 先前,系統並不會針對函式定義發出這類錯誤。 不過,如果不合法的運算式或非常值類型是評估為常數運算式的一部分,仍可能被診斷出來。

在舊版的 Visual Studio 中,系統會編譯下列程式碼而不發出警告:

void g();

template<typename T>
struct S
{
    constexpr void f();
};

template<>
constexpr void S<int>::f()
{
    g(); // C3615 in 15.9
}

在 Visual Studio 2017 15.9 版中,程式碼會引發錯誤 C3615

error C3615: constexpr function 'S<int>::f' cannot result in a constant expression.
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'g'.

若要避免此錯誤,請從函式 f() 的明確具現化移除 constexpr 限定詞。

另請參閱

Microsoft C/C++ 語言一致性