Share via


Visual Studio 2017의 C++ 규칙 향상, 동작 변경 및 버그 수정

Visual Studio의 Microsoft C/C++(MSVC)는 모든 릴리스에서 규칙을 개선하고 버그를 수정합니다. 이 문서에는 주 릴리스와 버전별 개선 사항이 나와 있습니다. 특정 버전의 변경 내용으로 바로 이동하려면 이 문서의 내용 아래의 목록을 사용하세요.

이 문서에는 Visual Studio 2017의 변경 내용이 나와 있습니다. Visual Studio 2022의 변경 내용에 대한 가이드는 Visual Studio 2022의 C++ 규칙 향상을 참조하세요. Visual Studio 2019의 변경 내용에 대한 지침은 Visual studio 2019의 C++ 규칙 향상을 참조하세요. 이전 규칙 향상의 전체 목록은 2003부터 2015까지 Visual C++의 새로운 기능을 참조하세요.

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 로 선언된 식이 특정 종류의 선언, if 및 switch 문, loop 문, 수명이 constexpr 식 계산 내에서 시작된 개체의 변경을 포함할 수 있습니다. constexpr 비정적 멤버 함수가 암시적으로 const 여야 하는 요구 사항이 더 이상 없습니다. 자세한 내용은 constexpr 함수에 대한 제약 조건 완화를 참조하세요.

C++17: 간결한 static_assert

static_assert용 메시지 매개 변수는 선택 사항입니다. 자세한 내용은 N3928: static_assert 확장, v2를 참조하세요.

C++17: [[fallthrough]] 특성

/std:c++17 모드 이상에서, [[fallthrough]] 특성은 제어 이동 동작이 의도된 컴파일러에 대한 힌트로서 switch 문의 컨텍스트에서 사용될 수 있습니다. 이 특성을 사용하면 컴파일러가 이러한 경우에 경고를 실행하지 않습니다. 자세한 내용은 P0188R0 - Wording for [[fallthrough]] attribute를 참조하세요.

일반화된 범위 기반 for 루프

범위 기반 for 루프에 동일한 형식의 begin()end() 반환 개체가 더 이상 필요하지 않습니다. 이 변경으로 인해 end()range-v3 및 완료되었지만 아직 게시되지 않은 범위 기술 사양의 범위에서 사용된 sentinel을 반환할 수 있습니다. 자세한 내용은 P0184R0 - Generalizing the Range-Based for Loop를 참조하세요.

Copy-list-initialization

Visual Studio 2017은 이니셜라이저 목록을 사용하여 개체 생성과 관련된 컴파일러 오류를 올바르게 발생시킵니다. 이러한 오류는 Visual Studio 2015에서 catch되지 않으며, 충돌 또는 정의되지 않은 런타임 동작이 발생할 수 있습니다. 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에서 컴파일러는 일반 복사 초기화와 같은 방식으로 복사 목록 초기화를 잘못 처리했고 오버로드 확인을 위해 생성자 변환만 고려했습니다. 다음 예제에서 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
}

사용되지 않는 형식 정의

Visual Studio 2017에서는 이제 클래스 또는 구조체에서 선언된 사용되지 않는 형식 정의에 대한 올바른 경고를 표시합니다. 다음 예제는 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

Visual Studio 2017에서는 조건부 계산 연산의 왼쪽 피연산자가 constexpr 컨텍스트에서 유효하지 않을 경우 올바르게 오류를 발생시킵니다. 다음 코드는 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에서 printf와 같이 variadic 함수에 전달된 클래스 또는 구조체는 일반적으로 복사 가능합니다. 해당 개체를 전달할 때 컴파일러는 비트 복사본을 만들기만 하고 생성자 또는 소멸자를 호출하지 않습니다.

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

SFINAE 식

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 이하에서는 컴파일러가 값 클래스 멤버에 대한 기본 멤버 이니셜라이저를 허용하지만 무시했습니다. 값 클래스의 기본 초기화는 항상 멤버를 0으로 초기화합니다. 기본 생성자는 허용되지 않습니다. 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를 사용하여 문제를 해결할 수 있었습니다. default가 C++11에서 키워드로 도입된 이후 해결 방법 자체가 문제가 되었습니다. 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 람다

이제 상수 식에서 람다 식을 사용할 수 있습니다. 자세한 내용은 C++의 constexpr 람다 식을 참조하세요.

함수 템플릿의 if constexpr

함수 템플릿에 컴파일 시간 분기가 가능하도록 if constexpr 문이 포함될 수 있습니다. 자세한 내용은 if constexpr을 참조하세요.

이니셜라이저를 사용하는 선택 문

if 문은 문 자체 내의 블록 범위에서 변수를 소개하는 이니셜라이저를 포함할 수 있습니다. 자세한 내용은 이니셜라이저가 있는 if을 참조하세요.

[[maybe_unused]][[nodiscard]] 특성

새 특성 [[maybe_unused]]는 엔터티가 사용되지 않는 경우 경고를 해제합니다. [[nodiscard]] 특성은 함수 호출의 반환 값이 무시되는 경우 경고를 만듭니다. 자세한 내용은 C++ 특성을 참조하세요.

반복 없이 특성 네임스페이스 사용

특성 목록에서 단일 네임스페이스 식별자만 사용하는 새 구문입니다. 자세한 내용은 C++ 특성을 참조하세요.

구조적 바인딩

이제 값이 배열, std::tuple 또는 std::pair이거나 모두 공용 비정적 데이터 멤버만 포함하는 경우 단일 선언에서 구성 요소의 값을 개별 이름으로 저장할 수 있습니다. 자세한 내용은 P0144R0 - Structured Bindings함수에서 여러 값 반환을 참조하세요.

enum class 값의 생성 규칙

이제 축소되지 않는 범위가 지정된 열거형이 암시적으로 변환됩니다. 그러면 범위가 지정된 열거형의 기본 형식이 열거형 자체로 변환됩니다. 변환은 해당 정의가 열거자를 도입하지 않는 경우와 소스가 목록 초기화 구문을 사용하는 경우에 사용할 수 있습니다. 자세한 내용은 P0138R2 - Construction Rules for enum class Values열거를 참조하세요.

값 기준 *this 캡처

람다 식의 *this 개체는 이제 값을 기준으로 캡처할 수 있습니다. 이 변경으로 인해 특히 최신 머신 아키텍처에서 병렬 및 비동기 작업을 통해 람다가 호출되는 시나리오를 사용할 수 있습니다. 자세한 내용은 P0018R3 - Lambda Capture of *this by Value as [=,*this]를 참조하세요.

bool에 대해 operator++ 제거

operator++는 더 이상 bool 형식에서 지원되지 않습니다. 자세한 내용은 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
}

오류를 수정하려면 iint로 선언합니다.

형식 특성에 대한 전제 조건 검사

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

네이티브에서 관리로의 마샬링에 대한 새로운 컴파일러 경고 및 런타임 검사

관리형 함수에서 네이티브 함수로 호출하려면 마샬링이 필요합니다. CLR은 마샬링을 수행하지만 C++ 의미 체계를 이해하지 못합니다. 네이티브 개체를 값으로 전달하면 CLR은 개체의 복사 생성자를 호출하거나, 런타임에 정의되지 않은 동작을 유발할 수 있는 BitBlt를 사용합니다.

이제 컴파일러는 컴파일 시간에 다음 오류를 발견하는 경우 경고를 내보냅니다. 삭제된 복사 생성자를 포함하는 네이티브 개체가 네이티브 경계와 관리되는 경계 사이에 값으로 전달됨. 컴파일러는 컴파일 시간에 알 수 없는 이러한 경우를 위해 잘못된 양식의 마양샬링이 수행될 때 프로그램에서 즉시 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 의 주소를 사용하거나 lvalue 참조에 바인딩할 수 없습니다. 이전 버전의 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에서 m이 "복합 멤버 지정자"인 offsetof(T, 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에서 m이 정적 데이터 멤버 또는 멤버 함수를 참조하는 offsetof(T, 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 릴리스에는 초기화되지 않은 const 변수에 대해 C++ 컴파일러가 진단을 실행하지 않는 재발 문제가 있었습니다. 이 재발 문제는 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>가 계산됩니다.

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

비공개 소멸자는 형식을 non-constructible로 만듭니다. 다음 선언이 작성된 것처럼 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;
};

문제를 해결하려면 선언과 같은 순서가 되도록 이니셜라이저 목록을 정렬합니다. 하나 또는 두 이니셜라이저가 기본 클래스 구성원을 참조하는 경우 유사한 경고가 발생합니다.

이 경고는 기본적으로 꺼져 있으며, /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_fnnot1not2를 대체합니다.

enable_shared_from_this 표현 수정

C++11에서 P0033R1enable_shared_from_this가 추가되었습니다. C++17 표준은 비정상적인 특정 사례를 더 잘 처리하도록 사양을 업데이트합니다. [14]

맵과 집합 스플라이스

P0083R3 이 기능은 연관 컨테이너에서 map, set, unordered_map, unordered_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 라이브러리 기본 사항에서 C++17로 shared_ptr 변경 사항 병합. [14]

배열에 대한 shared_ptr 수정

P0497R0 배열에 대한 shared_ptr 지원 해결입니다. [14]

insert_return_type의 명확한 정의

P0508R0 고유한 키가 포함된 연관 컨테이너와 고유한 키가 포함된 불규칙 컨테이너에는 중첩 형식 insert_return_type을 반환하는 멤버 함수 insert가 있습니다. 이 반환 형식은 이제 컨테이너의 반복기와 노드 형식에서 매개 변수화되는 형식의 특수화로 정의됩니다.

표준 라이브러리의 인라인 변수

P0607R0의 경우 표준 라이브러리에 선언된 몇 가지 공통 변수가 이제 인라인으로 선언됩니다.

부록 D 기능 사용 중단

C++ 표준의 부록 D에는 사용이 중단된 모든 기능이 포함되어 있습니다(shared_ptr::unique(), <codecvt>namespace std::tr1 포함). /std:c++17 이상의 컴파일러 옵션을 설정하면 부록 D의 거의 모든 표준 라이브러리 기능이 사용되지 않는 것으로 표시됩니다. 자세한 내용은 부록 D의 표준 라이브러리 기능이 사용되지 않는 것으로 표시됨을 참조하세요.

<experimental/filesystem>std::tr2::sys 네임스페이스는 이제 기본적으로 /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 및 non-const와 pack 및 non-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 가 사용될 경우 char* 또는 wchar_t* 처리기를 더 이상 문자열 리터럴에 일치시키지 않습니다.

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(...) 특성을 무시했습니다. 이 동작은 사용자가 의도하지 않은 코드 생성을 유발했으며 런타임에도 영향을 줄 가능성이 있었습니다. C4768 경고는 Visual Studio 15.3 버전에서 추가되었지만 기본적으로 꺼져 있었습니다. 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

이 새로운 경고 C4768은 Visual Studio 2017 15.3 이하 버전과 함께 제공된 일부 Windows SDK 헤더에서 표시됩니다(예: RS2 SDK라고도 하는 10.0.15063.0 버전). 그러나 이후 버전의 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 사용 시 포인터 형식을 비교해야 합니다. 하나의 형식이 추상적일 경우 non-pointer-type 비교가 실패할 수 있기 때문입니다.

#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 또는 이전 버전을 준수하는 코드와 소스 호환성 문제가 발생할 수 있습니다. 전체적으로 C++17 모드를 사용 중일 때 /Zc:noexceptTypes- 스위치를 사용하여 C++14 버전의 noexcept 로 되돌릴 수 있습니다. 이렇게 하면 모든 throw() 코드를 동시에 다시 작성하지 않고도 C++17을 준수하도록 소스 코드를 업데이트할 수 있습니다.

또한 컴파일러는 이제 새로운 경고 C5043과 관련하여 C++17 모드의 선언이나 /permissive-를 사용하여 더 많은 불일치 예외 사양을 진단합니다.

다음 코드는 Visual Studio 2017 버전 15.5에서 /std:c++17 스위치가 적용될 때 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 를 사용하면 pointers-to-noexcept-functions에 대한 부분 특수화가 누락되어 일치하는 특정 "호출 가능" 형식이 컴파일되지 않거나 기본 템플릿을 선택하지 못할 수 있습니다.

이러한 경우 noexcept 함수 포인터 및 멤버 함수에 대한 noexcept 포인터를 처리할 부분 특수화를 더 추가해야 할 수 있습니다. 이러한 오버로드는 /std:c++17 모드 이상에서만 적합합니다. C++14와 호환성을 유지해야 하고, 다른 사람이 사용할 코드를 작성 중인 경우 #ifdef 지시문 내에서 이러한 새 오버로드를 보호해야 합니다. 자체 포함 모듈에서 작업 중인 경우 #ifdef 가드를 사용하는 대신 /Zc:noexceptTypes- 스위치를 사용하여 컴파일할 수 있습니다.

다음 코드는 /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>'
}

다음 코드는 컴파일러가 새로운 부분 특수화 A<void (*)() noexcept>를 선택하기 때문에 /std:c++17 하에서 성공합니다.

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/tuple>, <experimental/optional>, <experimental/functional>, <experimental/any>, <experimental/string_view>, <experimental/memory>, <experimental/memory_resource>, <experimental/algorithm>의 업데이트를 포함합니다.

C++17: 표준 라이브러리의 클래스 템플릿 인수 추론 향상

P0739R0adopt_lock_tscoped_lock에 대한 매개 변수 목록의 앞으로 이동하여 scoped_lock의 일관된 사용을 활성화합니다. 복사 할당을 사용할 수 있도록 std::variant 생성자가 더 많은 사례에서 오버로드 해결에 참여하도록 허용합니다.

15.7의 규칙 향상

C++17: 상속 생성자 표현 수정

P0136R1은 생성자의 이름을 지정하는 using 선언에서 이제 더 많은 파생 클래스 생성자를 선언하는 대신 파생 클래스의 초기화를 볼 수 있는 해당 기본 클래스 생성자를 만들도록 지정합니다. 이 표현 수정은 C++14의 변경 내용입니다. Visual Studio 2017 버전 15.7 이상의 /std:c++17 모드 이상에서는, 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).

다음 예제에서는 Visual Studio 15.7에서 /std:c++17 동작을 보여 줍니다.

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

기본 클래스의 생성자가 public이 아니지만 파생 클래스에 액세스할 수 있는 경우, 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 선언 때문에 성공했습니다. 다음 예제에서는 /std:c++17 모드의 Visual Studio 버전 15.7에서 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: 자동으로 비형식 템플릿 매개 변수 선언

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
}

다음 예제에서는 /std:c++17 모드의 Visual Studio 15.7에서 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: 불필요한 decay 방지(부분)

P0777R1 "decay"의 개념과 const 또는 참조 한정자의 단순한 제거 간의 차이를 추가합니다. 새 형식 특성 remove_reference_t는 일부 컨텍스트에서 decay_t를 대체합니다. remove_cvref_t 지원은 Visual Studio 2019에서 구현되었습니다.

C++17: 병렬 알고리즘

P0024R2 병렬 처리 TS는 약간의 수정으로 표준으로 통합됩니다.

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

P0030R1float , doublelong double 형식에 대해 std::hypot에 세 개의 오버로드를 추가합니다(각각 3개의 입력 매개 변수를 가짐).

C++17: <filesystem>

P0218R1 몇 가지 단어 수정으로 파일 시스템 TS를 표준으로 채택합니다.

C++17: 수학 특수 함수

P0226R1 수학 특수 함수에 대한 이전 기술 사양을 표준 <cmath> 헤더로 채택합니다.

C++17: 표준 라이브러리에 대한 추론 가이드

P0433R2 클래스 탬플릿 인수 추론에 대한 지원을 추가하는 P0091R3의 C++17 도입의 이점을 활용하기 위해 STL로 업데이트합니다.

C++17: 기본 문자열 변환 복구

P0682R1 P0067R5의 새 기본 문자열 변환 함수를 새 헤더 <charconv>로 이동하고 std::error_code 대신 std::errc를 사용하여 오류 처리 변경을 포함하는 다른 개선 사항을 만듭니다.

C++17: char_traits에 대한 constexpr(부분)

P0426R1 상수 식에서 std::string_view를 사용할 수 있도록 std::traits_type 멤버 함수 length, comparefind를 변경합니다. (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

Visual Studio 2017 버전 15.7의 /std:c++17 모드에서는 기본 인수가 무시되지 않습니다.

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::typeB<T>::type으로 확인합니다.

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

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

Visual Studio 2017 버전 15.7의 /std:c++17 모드에서는 D의 문에 usingtypename 키워드가 필요합니다. 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++17 모드에서는 C4834의 경고 수준이 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 업데이트 3 이상에서는 이전 예제가 이제 C2078을 생성합니다. 다음 예제는 코드를 수정하는 방법을 보여 줍니다. 중첩된 brace-init-lists를 사용하여 std::array를 초기화할 때 내부 배열에 자체 braced-list를 제공합니다.

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

2단계 이름 조회 진단

2단계 이름 조회를 위해서는 템플릿 본문에 사용된 종속되지 않은 이름이 정의 시점에 템플릿에 표시될 수 있어야 합니다. 이전에는 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 라이브러리에는 unwind_type.hpp의 템플릿 정방향 선언에 대한 MSVC 관련 해결 방법이 있었습니다. Visual Studio 2017 버전 15.8(_MSC_VER==1915)부터 /permissive- 모드에서 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++17/std:c++latest 모드의 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

지금까지 offsetofreinterpret_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;
    }
}

이 오류를 해결하려면 <cstddef>를 통해 정의된 대로 offsetof를 사용합니다.

#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 키워드로 시작해야 합니다.

/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을 생성합니다. 이러한 연산자의 왼쪽에서 오른쪽 순서 요구 사항이 C++17에서 도입되었기 때문에 /std: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'

별칭 및 추론되지 않은 컨텍스트와 관련된 부분 순서

추론되지 않은 컨텍스트에서 별칭을 포함하는 부분 순서 지정 규칙의 구현에 차이가 있습니다. 다음 예제에서 Clang이 코드를 허용하는 반면 /permissive- 모드의 GCC 및 Microsoft C++ 컴파일러는 오류를 발생시킵니다.

#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를 사용하여 특정 오버로드를 제거하는 것이 좋습니다. 다음 예제에서 AllocA의 특수화인 경우 IsA 도우미 클래스를 사용하여 첫 번째 오버로드를 제거합니다.

#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++ 언어 규칙