01
operator saluran pipa02
Menerapkan _f
literal khusus03
Menerapkan print
dan spesialisasi std::formatter
04
Ubah template kelas yang diberikan sehingga memiliki ID yang berbeda untuk setiap tipe instantiasi yang berbeda05
tipe scope_guard
06
std::atomic
07
throw new MyException
08
panduan derivasi array
09
10
Lintasi anggota data kelas mana punC++17
C++20
11
Masalah dengan emplace_back()
12
make_vector()
13
return std::move
14
Memodifikasi objek yang dideklarasikan di namespace dengan metode khusus15
templat ekspresi16
Macro untuk membuat template fungsi transferTampilan pekerjaan rumah Luthor.
Mengirimkan PR tidak boleh mengubah README
saat ini. Silakan kirimkan pekerjaan ke src群友提交
.
Anda harus membuat file .md
atau .cpp
Anda sendiri di src群友提交第01题
Nama file harus diberi nama sesuai ID grup komunikasi Anda (atau nama pengguna GitHub, sehingga Anda dapat menemukannya dengan mudah) .
Syarat umum menjawab soal adalah sebagai berikut (perhatikan syarat tambahan soal):
main
tidak diubah, tidak boleh dihentikan jalannya (artinya jangan dimanfaatkan).01
operator saluran pipa Tanggal: 2023/7/21
Penanya: mq白
Diberikan kode:
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
1 4 9
Dijawab oleh: andyli
# include < algorithm >
# include < vector >
# include < functional >
# include < iostream >
template < typename R, typename F>
auto operator |(R&& r, F&& f) {
for ( auto && x: r)
f (x);
return r;
}
int main () {
std::vector v{ 1 , 2 , 3 };
std::function f{[]( const int & i) { std::cout << i << ' ' ; }};
auto f2 = []( int & i) { i *= i; };
v | f2 | f;
}
Itu normal, tidak masalah.
Dijawab oleh: mq松鼠
# include < iostream >
# include < vector >
# include < functional >
auto operator | (std::vector< int >&& v,std::function< void ( const int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
auto operator | (std::vector< int >& v,std::function< void ( int &)> f){
for ( auto &i:v){
f (i);
}
return v;
}
int main (){
std::vector v{ 1 , 2 , 3 };
std::function f {[]( const int & i) {std::cout << i << ' n ' ; } };
auto f2 = []( int & i) {i *= i; };
v | f2 | f;
}
Komentar: Jika saya tidak ada pekerjaan, saya akan menulis lebih banyak lagi dan membingkainya.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //可加可不加,不会就不加
std::vector<U>& operator |(std::vector<U>& v1, F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
Tanpa menggunakan templat :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
Daripada menggunakan for
yang tercakup, gunakan templat fungsi singkatan C++20:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
Paradigma berbagai jawaban lain tidak lebih dari perubahan-perubahan tersebut, sehingga tidak perlu ditulis lagi.
Jelas kita perlu membebani operator pipa |. Menurut formulir pemanggilan kita v | f2 | f
, rangkaian panggilan ini, dan berdasarkan hasil yang dijalankan, kita dapat mengetahui bahwa fungsi yang kelebihan beban harus mengembalikan referensi ke v, dan v. akan dimodifikasi. v | f2
memanggil operator |
. Operator |. menggunakan f2 untuk melintasi setiap elemen di v, lalu mengembalikan referensi v, dan kemudian f.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
Jika Anda belum pernah melihat ekspresi kendala, tidak masalah. Kami akan memperkenalkannya secara singkat di bawah.
Ekspresi require seperti fungsi yang mengembalikan bool, dan U dan F diisi dalam daftar parameter aktual std::regular_invocable sebagai tipe. Selama tipe U dan F memenuhi ekspresi, ia mengembalikan nilai true jika tidak , ia mengembalikan false, yang disebut "kendala tidak terpenuhi". Tipe yang tidak memenuhi batasan secara alami tidak akan mengeksekusi kode berikutnya.
Sedangkan untuk std::regular_invocable, kita cukup menganggapnya sebagai pasangan dari setiap nilai bertipe U. Apakah kita dapat memanggil fungsi F, yaitu memanggil std::invoke
.
Ini setara dengan kita membayangkan runtime pada waktu kompilasi dan membayangkan apakah U dapat mengeksekusi F pada runtime. Jika ya, maka hal tersebut memenuhi batasan yang ada.
Fungsi badannya sangat sederhana
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
Ekspresi rentang for (auto& i : v1)
seperti for(auto i=v.begin();i!=v.end();++i){f(*i)}
: kita memiliki vektor (range ) menerapkan fungsi f satu kali ke setiap elemen di dalamnya. Kembali ke v1 seperti biasa.
Jika kita tidak menggunakan templat, daftar parameter formal kita harus menggunakan std::function untuk menangkap fungsi yang kita gunakan:
Menerapkan f ke setiap anggota rentang tidak memerlukan nilai kembalian dan memerlukan modifikasi elemen dalam rentang, sehingga parameter kedua adalah std::function<void(int&)>
. Dan kita tidak perlu memodifikasi atau menyalin fungsi yang diteruskan f , jadi sebaiknya tambahkan batasan const .
Demikian pula, kita tidak dapat menggunakan range for tetapi std::ranges::for_each(v1, f);
yang lebih sederhana yaitu, terapkan fungsi f satu kali ke setiap elemen dalam rentang v1 seperti di atas.
Untuk bentuk penggunaan template, kita dapat menggunakan template fungsi singkatan C++20. Singkatnya, auto placeholder di daftar parameter fungsi akan menambahkan parameter template dummy ke daftar parameter template. Bentuk template awal dapat ditulis sebagai
std::vector< int >& operator |( auto & v1, const auto & f)
Bentuknya sama dengan aslinya.
02
Menerapkan _f
literal khusus Tanggal: 2023/7/22
Penanya: mq白
Diberikan kode:
int main (){
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
乐 :5 *
乐 :5 5 *
乐 :1010101 *
卢瑟******
6
π:3.141593
6
adalah masukan dan penentu
Dijawab oleh: andyli
# include < format >
# include < iostream >
# include < string_view >
# include < string >
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
int main () {
std::cout << "乐 :{} * n " _f ( 5 );
std::cout << "乐 :{0} {0} * n " _f ( 5 );
std::cout << "乐 :{:b} * n " _f ( 0b01010101 );
std::cout << " {:*<10} " _f ( "卢瑟" );
std::cout << ' n ' ;
int n{};
std::cin >> n;
std::cout << " π:{:.{}f} n " _f (std::numbers::pi_v< double >, n);
}
constexpr auto operator " " _f( const char * fmt, size_t ) {
return [=]< typename ... T>(T&&... Args) { return std::vformat (fmt, std::make_format_args (Args...)); };
}
Kita perlu menggunakan literal yang ditentukan pengguna C++11, dan ""_f
adalah literal yang ditentukan pengguna.
Namun, daftar parameter formal dari operator literal (fungsi yang dipanggil oleh literal yang ditentukan pengguna disebut operator literal) memiliki beberapa batasan. Yang kita perlukan adalah daftar parameter formal seperti const char *, std::size_t
, yang mana kebetulan ini diperbolehkan; tipe kembalian dari operator literal perlu disesuaikan, dan tipe ini perlu membebani operator()
secara internal untuk memenuhi persyaratan di atas agar literal dapat dipanggil seperti fungsi.
Mari kita lakukan langkah demi langkah:
void operator " " _test( const char * str, std:: size_t ){
std::cout << str << ' n ' ;
}
" luse " _test; //调用了字面量运算符,打印 luse
std:: size_t operator " " _test( const char * , std:: size_t len){
return len;
}
std:: size_t len = " luse " _test; //调用了字面量运算符,返回 luse 的长度 4
Dua contoh penggunaan kode di atas menunjukkan penggunaan dasar literal yang ditentukan pengguna, berikan perhatian khusus pada paragraf kedua, nilai kembalian . Jika Anda ingin menyebutnya seperti "xxx"_f(xxx)
, Anda harus melakukan sesuatu dengan tipe pengembalian.
struct X {
std:: size_t operator ()(std:: size_t n) const {
return n;
}
};
X operator " " _test( const char * , std:: size_t ){
return {};
}
std::cout<< "无意义" _test( 1 ); //打印 1
Kode sederhana di atas dengan sempurna melengkapi formulir pemanggilan yang kita butuhkan, maka sekarang saatnya untuk menyelesaikan fungsi yang diperlukan oleh pertanyaan tersebut. Cara termudah adalah dengan menggunakan pustaka format C++20 secara langsung untuk memformat.
namespace impl {
struct Helper {
const std::string_view s;
Helper ( const char * s, std:: size_t len): s(s, len) {}
template < typename ... Args>
std::string operator ()(Args&&... args) const {
return std::vformat (s, std::make_format_args (args...));
}
};
} // namespace impl
impl::Helper operator " " _f( const char * s, std:: size_t len) noexcept {
return {s, len};
}
operator""_f
sendiri sangat sederhana. Ini hanya digunakan untuk membuat objek impl::Helper
dari parameter yang masuk (format string) dan panjangnya lalu mengembalikannya. Tipe Helper
menggunakan string_view
sebagai anggota data untuk menyimpan string format untuk pemformatan nanti.
Fokusnya hanya pada operator()
. Ini adalah templat parameter variabel, digunakan untuk menerima jenis dan jumlah parameter apa pun yang kita lewati, dan kemudian mengembalikan string yang diformat.
Yang digunakan disini adalah std::vformat
untuk memformat. Parameter pertamanya adalah format string, yaitu aturan apa yang ingin kita format; parameter kedua adalah parameter yang akan diformat, tetapi kita tidak ada cara untuk secara langsung perluas paket parameter formal. Jenis parameter kedua sebenarnya std::format_args
. Kita harus menggunakan fungsi std::make_format_args
untuk meneruskan parameter kita. Ini akan mengembalikan tipe std::format_args
. Faktanya, ini setara dengan konversi, dan itu masuk akal.
Namun yang jelas jawaban standarnya tidak seperti ini, dan dapat disederhanakan hanya dengan membiarkan ""_f
mengembalikan ekspresi lambda.
03
Menerapkan print
dan spesialisasi std::formatter
Tanggal: 2023/7/24
Penanya: mq白
Menerapkan print
, jika Anda mengerjakan tugas sebelumnya, saya yakin ini sederhana. Formulir panggilan yang diperlukan adalah:
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
Diberikan tipe khusus Frace
, minta dukungan
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
Pemrograman berorientasi hasil, penggunaan makro, dll format
, dengan maksimal B
(mengacu pada evaluasi).
Tip: std::formatter
Cara terbaik adalah mengirimkan kode dengan tangkapan layar dari tiga platform yang dikompilasi secara online, seperti:
template <>
struct std ::formatter<Frac>:std::formatter< char >{
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
Kami hanya mendukung formulir yang diperlukan oleh pertanyaan dan mengkhususkan std::formatter
. Jika kami ingin mendukung pemformatan seperti {:6}
, ini jelas tidak memungkinkan. Spesialisasi dan formulir sederhana yang didukung oleh std::formatter
dapat ditemukan di dokumentasi . Beberapa spesialisasi kompleks telah ditulis sebelumnya. Di Cookbook , terdapat spesialisasi untuk std::ranges::range
dan std::tuple
, yang mendukung semua bentuk.
Menerapkan pencetakan sangat sederhana, kita hanya perlu mengikuti ide dari pertanyaan kedua. Untuk string yang diformat, gunakan std::string_view sebagai parameter formal pertama paket parameter formal.
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
Memanggil vformat
dengan cara ini akan mengembalikan sebuah string, yang dapat di-output secara langsung menggunakan cout.
Sedangkan untuk kustomisasi std::formatter
specialization, yang perlu kita ketahui adalah: jika ingin mengkustomisasi std::formatter template specialization, Anda perlu menyediakan dua fungsi yaitu parse dan format .
parse digunakan untuk memproses deskripsi format dan mengatur variabel anggota terkait. Untuk pertanyaan ini, kita tidak perlu bersusah payah mengimplementasikan fungsi anggota ini;
Kami memilih untuk mewarisi fungsi parse std::formatter<char>
dan mengimplementasikan fungsi format secara mandiri. Jika Anda tidak memahami sintaks spesialisasi templat di sini, tinjau Spesialisasi Templat.
template <>
struct std ::formatter<Frac> : std::formatter< char > {
auto format ( const auto & frac, auto & ctx) const { // const修饰是必须的
return std::format_to (ctx. out (), " {}/{} " , frac. a , frac. b );
}
};
Kita juga menggunakan auto sebagai templat fungsi singkatan untuk placeholder. Untuk fungsi format , parameter pertama adalah kelas khusus yang kita lewati, dan parameter kedua ( ctx ) adalah karakter format yang ingin kita teruskan ke iterator keluaran std::format_to
. rangkaian.
Di badan fungsi, kami langsung mengembalikan hasil ekspresi panggilan std::format_to()
. Fungsi ini mengembalikan iterator keluaran untuk nilai yang dikembalikan, kami menggunakan placeholder otomatis untuk menurunkan nilai kembalian.
Di antara parameter fungsi, ctx.out()
adalah iterator keluaran, parameter kedua adalah string format legal yang dapat dikonversi ke std::string_view
atau std::wstring_view
, dan hasil konversinya adalah ekspresi konstan dan Args. Pada pertanyaan ini kita mengisi form yang kita perlukan yaitu {}/{}
.
Kami ingin kedua parameter dimasukkan ke dalam {}
, sama seperti kami menggunakan printf(%d,x)
; dua parameter terakhir adalah "nilai yang perlu dimasukkan ke dalam {}
", yaitu parameter untuk diformat.
04
Ubah template kelas yang diberikan sehingga memiliki ID yang berbeda untuk setiap tipe instantiasi yang berbeda Tanggal: 2023/7/25
Penanya: Adttil
# include < iostream >
class ComponentBase {
protected:
static inline std:: size_t component_type_count = 0 ;
};
template < typename T>
class Component : public ComponentBase {
public:
// todo...
//使用任意方式更改当前模板类,使得对于任意类型X,若其继承自Component
//则X::component_type_id()会得到一个独一无二的size_t类型的id(对于不同的X类型返回的值应不同)
//要求:不能使用std::type_info(禁用typeid关键字),所有id从0开始连续。
};
class A : public Component <A>
{};
class B : public Component <B>
{};
class C : public Component <C>
{};
int main ()
{
std::cout << A::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << B::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << A::component_type_id () << std::endl;
std::cout << C::component_type_id () << std::endl;
}
0
1
1
0
0
2
Pengajuan harus memberikan hasil pengujian multi-platform, seperti yang ditunjukkan pada gambar:
template < typename T>
class Component : public ComponentBase {
public:
static std:: size_t component_type_id (){
static std:: size_t ID = component_type_count++;
return ID;
}
};
menganalisa:
Kita perlu mengimplementasikan fungsi anggota statis component_type_id
dari Component
. Hal ini diketahui dari kode yang diberikan:
class A : public Component <A>
{};
A::component_type_id ()
Pertanyaannya mengharuskan setiap tipe kelas khusus (diasumsikan X) mewarisi Component<X>
, dan memanggil component_type_id()
mengembalikan ID uniknya sendiri. Hal yang sama berlaku untuk tipe lainnya.
Sebelum menyelesaikan masalah, kita perlu menekankan satu hal pengetahuan:
Templat C++ bukan tipe spesifik, melainkan setelah instantiasi (yaitu, templat fungsi bukan fungsi, dan templat kelas bukan kelas ). Anggota statis atau fungsi anggota statis templat kelas bukan milik templat, tetapi milik tipe tertentu setelah instantiasi , Kami Anda dapat menggunakan sepotong kode untuk menunjukkan kesimpulan:
# include < iostream >
template < typename T>
struct Test {
inline static int n = 10 ;
};
int main (){
Test< int >::n = 1 ;
std::cout << Test< void >::n << ' n ' ; // 10
std::cout << Test< int >::n << ' n ' ; // 1
}
Kode ini dengan mudah menunjukkan bahwa anggota data statis termasuk dalam tipe tertentu setelah contoh templat . Test<void>::n
dan Test<int>::n
bukan n yang sama, dan Test<void>
dan Test<int>
bukan tipe yang sama (hal yang sama berlaku untuk fungsi anggota statis).
Jadi solusi kami menggunakan: jenis templat kelas Component
yang berbeda, yang juga merupakan fungsi anggota statis yang berbeda. Bagian statis dalam fungsi anggota statis juga unik dan hanya akan diinisialisasi saat dipanggil untuk pertama kali.
05
tipe scope_guard
Tanggal : 2023/7/29
Penanya : Da'Inihlus
Diperlukan untuk mengimplementasikan tipe scope_guard
(yaitu, mendukung penerusan tipe apa pun yang dapat dipanggil dan memanggilnya secara bersamaan selama penghancuran).
# include < cstdio >
# include < cassert >
# include < stdexcept >
# include < iostream >
# include < functional >
struct X {
X () { puts ( " X() " ); }
X ( const X&) { puts ( " X(const X&) " ); }
X (X&&) noexcept { puts ( " X(X&&) " ); }
~X () { puts ( " ~X() " ); }
};
int main () {
{
// scope_guard的作用之一,是让各种C风格指针接口作为局部变量时也能得到RAII支持
// 这也是本题的基础要求
FILE * fp = nullptr ;
try {
fp = fopen ( " test.txt " , " a " );
auto guard = scope_guard ([&] {
fclose (fp);
fp = nullptr ;
});
throw std::runtime_error{ " Test " };
} catch (std:: exception & e){
puts (e. what ());
}
assert (fp == nullptr );
}
puts ( " ---------- " );
{
// 附加要求1,支持函数对象调用
struct Test {
void operator ()(X* x) {
delete x;
}
} t;
auto x = new X{};
auto guard = scope_guard (t, x);
}
puts ( " ---------- " );
{
// 附加要求2,支持成员函数和std::ref
auto x = new X{};
{
struct Test {
void f (X*& px) {
delete px;
px = nullptr ;
}
} t;
auto guard = scope_guard{&Test::f, &t, std::ref (x)};
}
assert (x == nullptr );
}
}
Test
----------
X()
~X()
----------
X()
~X()
std::function
dan hapus tipenya struct scope_guard {
std::function< void ()>f;
template < typename Func, typename ...Args> requires std::invocable<Func, std:: unwrap_reference_t <Args>...>
scope_guard (Func&& func, Args&&...args) :f{ [func = std::forward<Func>(func), ... args = std::forward<Args>(args)]() mutable {
std::invoke (std::forward<std:: decay_t <Func>>(func), std:: unwrap_reference_t <Args>(std::forward<Args>(args))...);
} }{}
~scope_guard () { f (); }
scope_guard ( const scope_guard&) = delete ;
scope_guard& operator =( const scope_guard&) = delete ;
};
std::tuple
+ std::apply
template < typename F, typename ...Args>
requires requires (F f, Args...args) { std::invoke (f, args...); }
struct scope_guard {
F f;
std::tuple<Args...>values;
template < typename Fn, typename ...Ts>
scope_guard (Fn&& func, Ts&&...args) :f{ std::forward<Fn>(func) }, values{ std::forward<Ts>(args)... } {}
~scope_guard () {
std::apply (f, values);
}
scope_guard ( const scope_guard&) = delete ;
};
template < typename F, typename ...Args> //推导指引非常重要
scope_guard (F&&, Args&&...) -> scope_guard<std::decay_t<F>, std::decay_t<Args>...>;
06
std::atomic
Tanggal: 2023/8/2
Penanya: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
Jelaskan mengapa kode di atas dapat dikompilasi setelah C++17 tetapi tidak sebelum C++17?
Dalam std::atomic<int> n = 6
, karena 6
dan std::atomic<int>
bukan tipe yang sama (tetapi sebenarnya ada urutan konversi yang ditentukan pengguna di sini, Anda dapat berpikir bahwa 6
dapat dikonversi secara implisit ).
Artinya, memanggil konstruktor konversi:
constexpr atomic ( T desired ) noexcept ;
Konstruktor konversi juga digunakan sebagai bagian dari rangkaian konversi yang ditentukan pengguna
6
akan memanggil konstruktor konversi untuk membuat objek atom sementara untuk menginisialisasi n
secara langsung , yaitu
std::atomic< int > n (std::atomic< int >( 6 ))
Pada versi sebelum C++17 , wajar jika konstruktor penyalinan/pemindahan harus dicari dan dideteksi sebelum dapat dikompilasi jika memenuhi persyaratan. Tetapi: