C++ 17 Proposal P0012R1《Make exception specifications be part of the type system》導讀

C++ 17 Proposal P0127R2《Declaring non-type template parameters with auto》導讀

C++ 17 Proposal P0522R0《Matching of template template-arguments excludes compatible templates》導讀

C++ 17 Proposal P0035R4《Dynamic memory allocation for over-aligned data》導讀

C++ 17 Proposal P0195R2《Pack expansions in using-declarations》導讀

C++ 17 Proposal P0135R0《Guaranteed copy elision through simplified value categories》導讀

C++ 17 Proposal P0003R5《Removing Deprecated Exception Specifications from C++17》導讀

1. P0012R1《Make exception specifications be part of the type system》導讀

自 C++ 11 之後, 例外情況在 C++ 中變得非常重要. 特別是程式庫設計者, 大家都會特別注重例外情況的處理. 這篇 Proposal 提出, 讓例外情況標識稱為型別系統的一部分 :

#include <iostream>

using namespace std;
void func() noexcept {}
int main(int argc, char *argv[]) {
    cout << is_same_v<void (), decltype(func)> << endl;     //輸出 : 0
    cout << is_same_v<void () noexcept, decltype(func)> << endl;        //輸出 : 1
}

2. P0127R2《Declaring non-type template parameters with auto》導讀

auto 自 C++ 98 就存在, 當時它還是從 C 繼承過來的. 在 C++ 11 之後, auto 有了新的用途, 舊的用途被移除. 而在 C++ 14 之後, auto 被允許用於泛型 Lambda 表達式. 自 C++ 14 之後, auto 有了佔位的用途. 這篇 Proposal 提出, 讓非型別樣板參數也支援使用 auto 進行推導 :

include <iostream>

template <auto Value>
int x = 0;
template <>
int x<1> = 1;       //Value 被推導為 int
template <>
int x<2l> = 2;      //Value 被推導為 long
template <auto Value, auto ...Values>
struct value_list : value_list<Values...> {
    constexpr static auto value {Value};
    using type = value_list<Values...>;
};
template <auto Value>
struct value_list<Value> {
    constexpr static auto value {Value};
    using type = value_list<Value>;
};

using namespace std;
int main(int argc, char *argv[]) {
    using list = value_list<1, 2, 3>;
    cout << list::value << endl;        //輸出 : 1
    cout << list::type::value << endl;      //輸出 : 2
    cout << list::type::type::value << endl;        //輸出 : 3
    cout << list::type::type::type::value << endl;      //輸出 : 3
    cout << x<0> << endl;       //輸出 : 0
    cout << x<1> << endl;       //輸出 : 1
    cout << x<2> << endl;       //輸出 : 0
    cout << x<2l> << endl;      //輸出 : 2
}

3. P0522R0《Matching of template template-arguments excludes compatible templates》導讀

P0127R2 提出為 C++ 增加非型別樣板引數使用 auto 進行推導, 但是有一些特殊情況 :

#include <iostream>

using namespace std;
template <template <auto> typename>
void f_auto() {
    cout << "auto" << endl;
}
template <template <int> typename>
void f_int() {
    cout << "int" << endl;
}
template <auto>
struct s_auto {};
template <int>
struct s_int {};
int main(int argc, char *argv[]) {
    f_auto<s_int>();        //???
    f_int<s_auto>();        //???
    f_auto<f_auto>();       //輸出 : auto
}

在這種情況下, auto 如何推導成了問題, 從而導致編碼錯誤. P0522R0 提出讓這種情況通過編碼 :

#include <iostream>

using namespace std;
template <template <auto> typename>
void f_auto() {
    cout << "auto" << endl;
}
template <template <int> typename>
void f_int() {
    cout << "int" << endl;
}
template <auto>
struct s_auto {};
template <int>
struct s_int {};
int main(int argc, char *argv[]) {
    f_auto<s_int>();        //輸出 : auto
    f_int<s_auto>();        //輸出 : int
    f_auto<s_auto>();       //輸出 : auto
}

除此之外, 對於帶有預設引數的多個樣板參數, P0522R0 還提出讓以下程式碼通過編碼 :

template <template <typename> typename> void f();
template <typename, typename = void>
struct s;
int main(int argc, char *argv[]) {
    f<s>();     //Error before C++ 17, 但是 P0522R0 希望此通過編碼
}

但是對於 Clang, 對此並不支援. 在 GCC 下, 上述程式碼可以通過編碼

4. P0035R4《Dynamic memory allocation for over-aligned data》導讀

C++ 11 為類別引入了記憶體對位限定 alignas, 但是並沒有引入與之對應的正確記憶體配置方式. 為此, C++ 17 增加了一個限制作為範圍的列舉型別 :

namespace std {
    enum class align_val_t : size_t {};
}

並且增加了若干個 operator newoperator delete :

void *operator new(std::size_t, std::align_val_t);
void *operator new[](std::size_t, std::align_val_t);
void operator delete(void *, std::align_val_t);
void operator delete[](void *, std::align_val_t);
void operator delete(void *, std::size_t, std::align_val_t);
void operator delete[](void *, std::size_t, std::align_val_t);
void *operator new(std::size_t, std::align_val_t, const std::nothrow_t &) noexcept;
void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete(void *, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete[](void *, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete(void *, std::size_t, std::align_val_t, const std::nothrow_t &) noexcept;
void operator delete[](void *, std::size_t, std::align_val_t, const std::nothrow_t &) noexcept;

5. P0195R2《Pack expansions in using-declarations》導讀

對於下面不完整的程式碼 :

#include <iostream>

using namespace std;

template <typename ...Fs>
struct overloader : ... {
    //將 Fs... 中的函式呼叫運算子繼承過來
};
template <typename ...Fs>
constexpr auto make_overloader(Fs &&...f) {
    return overloader<Fs...> {forward<Fs>(f)...};
}
int main(int argc, char *argv[]) {
    auto o {make_overloader([](const auto &a) {
        cout << "auto : " << a << endl;
    }, [](float f) {
        cout << "float : " << f << endl;
    })};
    o(1.0f);        //輸出結果 : float : 1
    o(2);       //輸出結果 : auto : 2
}

如何補充才能通過編碼並且得到我們期望的輸出? 在 C++ 17 之前, 並不能直接使用 C++ 11 引入的 using 繼承, 需要遞迴地進行實作 :

#include <iostream>

using namespace std;

template <typename F, typename ...Fs>
struct overloader : F, overloader<Fs...> {
    using F::operator();
    using overloader<Fs...>::operator();
};
template <typename F>
struct overloader<F> : F {
    using F::operator();
};
template <typename ...Fs>
constexpr auto make_overloader(Fs &&...f) {
    return overloader<Fs...> {forward<Fs>(f)...};
}
int main(int argc, char *argv[]) {
    auto o {make_overloader([](const auto &a) {
        cout << "auto : " << a << endl;
    }, [](float f) {
        cout << "float : " << f << endl;
    })};
    o(1.0f);        //輸出結果 : float : 1
    o(2);       //輸出結果 : auto : 2
}

在 P0195R2 中提出, 簡化這樣的寫法, 允許 using 宣告中直接將型別包中的需要使用的函式繼承過來 :

#include <iostream>

using namespace std;

template <typename ...Fs>
struct overloader : Fs... {
    using Fs::operator()...;
};
template <typename ...Fs>
constexpr auto make_overloader(Fs &&...f) {
    return overloader<Fs...> {forward<Fs>(f)...};
}
int main(int argc, char *argv[]) {
    auto o {make_overloader([](const auto &a) {
        cout << "auto : " << a << endl;
    }, [](float f) {
        cout << "float : " << f << endl;
    })};
    o(1.0f);        //輸出結果 : float : 1
    o(2);       //輸出結果 : auto : 2
}

6. P0135R0《Guaranteed copy elision through simplified value categories》導讀

對於不可移動類別, 在 C++ 17 之前僅能通過動態記憶體配置來編寫工廠函式 :

struct non_moveable {
    non_moveable(non_moveable &&) = delete;
    non_moveable &operator=(non_moveable &&) = delete;
    //...
};
non_moveable make();        //會產生複製建構, 而並非是原地建構
non_moveable *make();       //省略一次複製建構, 但是使用了動態記憶體配置

除此之外, 如果允許原地建構的話, 上述程式碼還可以為 make 函式增加 noexcept 標識以方便編碼器進行優化. 對於動態記憶體配置版本的 make 函式, 一般來說不能為其增加 noexcept 標識

對於大型物件, 有時候, 移動帶來的消耗可能比重新建構更多

使用 auto 宣告變數時可能無法避免移動, 即使編碼器實際上不會呼叫移動操作

上面三個提到的問題, 都是目前 C++ 針對複製或者移動省略規範的不足, P0135R0 提出重新定義 glvalue 和 prvalue 的定義 (我們會在 C++ 標準導讀中仔細講解), 使得上面這些情況得到解決, 並且會讓 non_moveable 對應的非動態記憶體配置版本的 make 函式通過編碼, 並且進行原地建構而非使用動態記憶體配置

7. P0003R5《Removing Deprecated Exception Specifications from C++17》導讀

C++ 98 引入了動態例外情況 : throw(type...). 在 C++ 11 發布時, 例外情況的重要性已經得到絕大多數人的認可, 但是動態例外情況一直被認為是毫無用處的, 並且它還被認為是一個失敗的實驗

儘管移除了 throw(type...) 這樣的動態例外情況標識, 但是仍然保留了 throw() 這樣的標識, 並作為 noexcept(true) 的別名以相容舊的程式碼. 但是, throw() 並不建議使用, 而是應該由 noexcept 或者 noexcept 表達式替代