摘要訊息 : 一些 C++ 17 引入的小特性.
0. 前言
C++ 17 引入的絕大多數重要特性我們基本都介紹完了, 可以在 Jonny'Blog 中搜尋 C++ 17 閱覽這些文章. 還有一些小特性也是 C++ 17 引入的, 本文文章將通過羅列的方式介紹一部分特性.
更新紀錄 :
- 2022 年 6 月 7 日進行第一次更新和修正.
1. 函式型別增強
雖然 C++ 11 引入了 noexcept
標識, 但是它並沒有影響函式的型別. 也就是在 C++ 11 和 C++ 14 中, void () noexcept
和 void ()
是一樣的型別. C++ 17 提案 P0012R1《Make exception specifications be part of the type system》提出讓例外標識稱為型別系統的一部分. 因此自 C++ 17 起, void () noexcept
和 void ()
就不再是一樣的型別了.
2. 使用 auto
推導非型別樣板引數
auto
自 C++ 98 就存在, 當時它還是從 C 繼承過來的. 在 C++ 11 之後, auto
有了新的用途, 舊的用途被移除. 而在 C++ 14 之後, auto
被允許用於泛型 Lambda 表達式. 於是自 C++ 14 之後, auto
有了佔位的用途. C++ 17 提案 P0127R2《Declaring non-type template parameters with auto
》提出, 讓非型別樣板參數也支援使用 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
}
然而, 這樣會導致樣板樣板參數出現一些問題 :
#include <iostream>
template <template <auto> typename>
void f_auto() {
std::cout << "auto" << std::endl;
}
template <template <int> typename>
void f_int() {
std::cout << "int" << std::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<s_auto>(); // 輸出 : auto
}
在這種情況下, auto
如何推導成了問題, 從而導致編碼錯誤. C++ 17 提案 P0522R0《Matching of template template-arguments excludes compatible templates》提出讓這種情況通過編碼, 從而 f_auto<s_int>();
的輸出為 auto
, f_int<s_auto>();
的輸出為 int
.
除此之外, 對於帶有預設引數的多個樣板參數, P0522R0 還提出讓以下程式碼通過編碼 :
template <template <typename> typename> void f();
template <typename, typename = void>
struct s;
int main(int argc, char *argv[]) {
f<s>(); // C++ 17 之前必定會產生編碼錯誤, 但是 P0522R0 希望此通過編碼
}
但是 Clang 對此並不支援. 在 GCC 下, 上述程式碼可以通過編碼.
3. 帶有記憶體對位的記憶體配置函式
C++ 11 為類別引入了記憶體對位限定 alignas
, 但是並沒有引入與之對應的正確記憶體配置方式. 為此, C++ 17 提案 P0035R4《Dynamic memory allocation for over-aligned data》提出在名稱空間 std
中增加了一個限制作為範圍的列舉型別 : enum class std::align_val_t : std::size_t {};
. 在標頭檔 <memory>
增加了若干個和記憶體對位有關的 operator new
和 operator 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;
4. 從型別包中繼承
#include <iostream>
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) {
std::cout << "auto : " << a << std::endl;
}, [](float f) {
std::cout << "float : " << f << std::endl;
})};
o(1.0f); // 輸出結果 : float : 1
o(2); // 輸出結果 : auto : 2
}
如何補充 Code 5-1 才能通過編碼並且得到我們期望的輸出? 在 C++ 17 之前, 並不能直接使用 C++ 11 引入的 using
繼承, 需要遞迴地進行實作 :
#include <iostream>
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) {
std::cout << "auto : " << a << std::endl;
}, [](float f) {
std::cout << "float : " << f << std::endl;
})};
o(1.0f); // 輸出結果 : float : 1
o(2); // 輸出結果 : auto : 2
}
C++ 17 提案 P0195R2《Pack expansions in using-declarations》提出允許 using
宣告中直接將型別包中的需要使用的函式繼承過來 :
#include <iostream>
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) {
std::cout << "auto : " << a << std::endl;
}, [](float f) {
std::cout << "float : " << f << std::endl;
})};
o(1.0f); // 輸出結果 : float : 1
o(2); // 輸出結果 : auto : 2
}
5. 重新定義廣義左值和純右值
對於不可移動類別, 在 C++ 17 之前僅能通過動態記憶體配置來編寫工廠函式 :
struct non_moveable {
non_moveable(non_moveable &&) = delete;
non_moveable &operator=(non_moveable &&) = delete;
//...
};
non_moveable make(); // 會產生複製建構, 而並非是原地建構
non_moveable *make(); // 省略一次複製建構, 但是使用了動態記憶體配置
除此之外, 如果允許原地建構的話, Code 6 還可以為 make
函式增加 noexcept
標識以方便編碼器進行優化. 對於動態記憶體配置版本的 make
函式, 一般來說不能為其增加 noexcept
標識.
對於大型物件, 有時候, 移動帶來的消耗可能比重新建構更多. 使用 auto
宣告變數有時候可能無法避免移動, 即使編碼器實際上不會呼叫移動操作. 結合 Code 6 和這兩個問題, 都是目前 C++ 針對複製或者移動省略規範的不足, C++ 17 提案 P0135R0《Guaranteed copy elision through simplified value categories》 提出重新定義廣義左值和純右值的定義, 使得上面這些情況得到解決, 並且會讓 non_moveable
對應的非動態記憶體配置版本的 make
函式通過編碼, 並且進行原地建構而非使用動態記憶體配置.
6. 移除動態例外情況
C++ 98 引入了動態例外情況 : throw(type...)
. 在 C++ 11 發布時, 例外情況的重要性已經得到絕大多數人的認可, 但是動態例外情況一直被認為是毫無用處的, 並且它還被認為是一個失敗的實驗.
儘管移除了 throw(type...)
這樣的動態例外情況標識, 但是仍然保留了 throw()
這樣的標識, 並作為 noexcept(true)
的別名以相容舊的程式碼. 但是, throw()
並不建議使用, 而是應該由 noexcept
或者 noexcept
表達式替代.
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :