01
パイプラインオペレーターを実装する02
カスタムリテラル_f
の実装03
print
実装とstd::formatter
の特殊化04
指定されたクラス テンプレートを変更して、異なるタイプのインスタンス化ごとに異なる ID を持つようにします。scope_guard
型05
std::atomic
初期化の06
07
throw new MyException
08
配列導出array
の定義09
検索問題10
任意のクラス データ メンバーを走査しますC++17
書き方C++20
書き方11
emplace_back()
の問題make_vector()
12
return std::move
13
14
特別なメソッドで名前空間で宣言されたオブジェクトを変更する15
式テンプレート16
マクロルーサーの宿題の展示。
PR を送信しても、現在のREADME
変更されません。たとえば、最初のジョブを送信する場合は、ジョブをsrc群友提交
に送信してください。
src群友提交第01题
に独自の.md
または.cpp
ファイルを作成する必要があります。ファイル名は、自分の通信グループ ID (または自分自身を簡単に見つけられるように GitHub ユーザー名) に基づいて命名する必要があります。
質問に回答するための一般的な要件は次のとおりです (質問の追加要件に注意してください)。
main
関数を変更しない場合は、その実行を停止してはなりません (つまり、main 関数を利用しないでください)。01
パイプラインオペレーターを実装する日付: 2023/7/21
質問者: mq白
コードを考えると:
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
回答者: 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;
}
それは正常です、問題ありません。
回答者: 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;
}
コメント: 何もすることがない場合は、さらにオーバーロードを書いてフレーム化します。
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;
}
テンプレートを使用しない場合:
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
スコープを指定したfor
使用する代わりに、C++20 短縮関数テンプレートを使用します。
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
他のさまざまな答えのパラダイムはこれらの変更にすぎないため、再度書く必要はありません。
明らかに、パイプライン演算子 | をオーバーロードする必要があります。呼び出し形式v | f2 | f
によれば、与えられた実行結果に基づいて、オーバーロードされた関数が v および v への参照を返す必要があることがわかります。変更されます。 v | f2
、 operator |
呼び出して v のすべての要素を調べ、次に | を返します。
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
制約式に触れたことがない場合でも問題ありません。以下で簡単に説明します。
Required 式は bool を返す関数のようなもので、型として U と F が std::regulator_invocable の実際のパラメータ リストに入力されます。条件を満たさない場合は true を返します。の場合は false が返され、これは「制約が満たされていない」と呼ばれます。制約を満たさない型は、当然、後続のコードを実行しません。
std::regulator_invocable については、単に U 型の各値のペアとして考えることができます。関数 F を呼び出すことができるかどうか、つまりstd::invoke
呼び出すことができるかどうか。
これは、コンパイル時に実行時を想像し、実行時に U が F を実行できるかどうかを想像するのと同じです。そうであれば、制約は満たされます。
関数本体は極めてシンプル
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
for (auto& i : v1)
範囲式はfor(auto i=v.begin();i!=v.end();++i){f(*i)}
ようなものです。ベクトル(range ) があります。 f関数をその中の各要素に 1 回適用します。通常どおり v1 に戻ります。
テンプレートを使用しない場合、仮パラメータ リストは std::function を使用して、使用する関数をキャッチする必要があります。
範囲の各メンバーにf を適用する場合、戻り値は必要ありませんが、範囲内の要素を変更する必要があるため、2 番目のパラメーターはstd::function<void(int&)>
になります。また、渡された関数f を変更したりコピーしたりする必要がないため、 const制限を追加することをお勧めします。
同様に、 range for は使用できませんが、より単純なstd::ranges::for_each(v1, f);
つまり、上記のように関数f をrange v1 内の各要素に 1 回適用します。
テンプレートを使用する形式では、C++20 略語関数テンプレートを使用できます。つまり、関数パラメーター リストの自動プレースホルダーは、ダミーのテンプレート パラメーターをテンプレート パラメーター リストに追加します。最初のテンプレート フォームは次のように記述できます。
std::vector< int >& operator |( auto & v1, const auto & f)
本来の形状と同じです。
02
カスタムリテラル_f
の実装日付: 2023/7/22
質問者: mq白
コードを考えると:
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
入力であり、決定されます。
回答者: 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...)); };
}
C++11 ユーザー定義リテラルを使用する必要があります。 ""_f
まさにユーザー定義リテラルです。
ただし、リテラル演算子の仮パラメータ リスト (ユーザー定義リテラルによって呼び出される関数はリテラル演算子と呼ばれます) にはいくつかの制限があります。必要なのはconst char *, std::size_t
などの仮パラメータ リストです。これはたまたま許可されていますが、リテラル演算子の戻り値の型はカスタマイズする必要があり、この型はリテラルを関数のように呼び出すための上記の要件を満たすために、内部でoperator()
をオーバーロードする必要があります。
ステップバイステップで進みましょう:
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
上記のコードの 2 つの使用例は、ユーザー定義リテラルの基本的な使用法を示しています。2 番目の段落である return valueに特に注意してください。 "xxx"_f(xxx)
のように呼び出したい場合は、戻り値の型を変更する必要があります。
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
上記の単純なコードは、必要な呼び出しフォームを完全に完成させた後、質問で必要な関数を完成させます。最も簡単な方法は、C++20 フォーマット ライブラリをフォーマットに直接使用することです。
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
自体は非常に単純です。これは、受信パラメータ (フォーマット文字列) と長さからimpl::Helper
オブジェクトを構築し、それを返すためにのみ使用されます。 Helper
タイプは、 string_view
データ メンバーとして使用し、後で書式設定するために書式文字列を保存します。
焦点はoperator()
のみにあります。これは可変パラメータ テンプレートであり、渡した任意のタイプと数のパラメータを受け取り、フォーマットされた文字列を返すために使用されます。
ここで使用されるのはフォーマット用のstd::vformat
です。その最初のパラメータはフォーマット文字列、つまり、どのようなルールに従ってフォーマットするかを指定しますが、直接フォーマットする方法はありません。仮パラメータ パッケージを展開します。その 2 番目のパラメータの型は実際にはstd::format_args
です。 パラメータを渡すにはstd::make_format_args
std::format_args
を使用する必要があります。実際、これは変換と同等であり、これは合理的です。
しかし、明らかに標準的な答えはこのようなものではなく、単純に""_f
にラムダ式を返させることで簡略化できます。
03
print
実装とstd::formatter
の特殊化日付: 2023/7/24
質問者: mq白
print
実装は、前の課題を行ったことがあれば、これは簡単だと思います。 必要な呼び出しフォームは次のとおりです。
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
カスタム タイプFrace
を指定して、サポートをリクエストしてください
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
結果重視のプログラミング、マクロの使用等は禁止されており、最大B
(評価を参照)となります。この課題では主にformat
ライブラリの調査と学習を行います。
ヒント: std::formatter
オンラインでコンパイルされた次の 3 つのプラットフォームのスクリーンショットを含むコードを送信するのが最善です。
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...));
}
質問で必要なフォームをサポートし、 std::formatter
特殊化するだけですが、 {:6}
などの書式設定をサポートしたい場合は、明らかにそれ以上の操作が必要になります。 std::formatter
でサポートされる単純な特殊化とフォームについては、ドキュメントを参照してください。 いくつかの複雑な特殊化は以前に up によって書かれており、すべての形式をサポートするstd std::ranges::range
およびstd::tuple
の特殊化があります。
print の実装は非常に簡単です。フォーマットされた文字列の場合は、最初の仮パラメータとして std::string_view を使用するだけです。仮パラメータパッケージ。
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
この方法でvformat
呼び出すと文字列が返され、これは cout を使用して直接出力できます。
std::formatter
特殊化のカスタマイズに関して知っておく必要があるのは、 std::formatterテンプレートの特殊化をカスタマイズしたい場合は、 parseとformat の2 つの関数を提供する必要があるということです。
parse は、形式の説明を処理し、関連するメンバー変数を設定するために使用されます。この質問では、このメンバー関数をわざわざ実装する必要はありません。
std::formatter<char>
のparse関数を継承し、 format関数を独立して実装することを選択します。ここでのテンプレートの特殊化の構文が理解できない場合は、「テンプレートの特殊化」を参照してください。
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 );
}
};
また、 format関数の場合、プレースホルダーの短縮関数テンプレートとしてauto を使用します。最初のパラメーターは渡すカスタム クラスで、2 番目のパラメーター ( ctx ) はstd::format_to
出力イテレーターに渡すフォーマット文字です。 。 弦。
関数本体では、 std::format_to()
呼び出し式の結果を直接返します。この関数は戻り値の出力イテレータを返します。戻り値の導出には自動プレースホルダーを使用します。
関数パラメータのうち、 ctx.out()
は出力反復子、2 番目のパラメータはstd::string_view
またはstd::wstring_view
に変換できる正当な書式文字列で、変換結果は定数式と Args です。この質問では、必要なフォーム、つまり{}/{}
に入力します。
printf(%d,x)
使用するのと同じように、2 つのパラメータを{}
に詰め込みます。最後の 2 つのパラメータは、「 {}
に詰め込む必要がある値」、つまり、フォーマットされる。
04
指定されたクラス テンプレートを変更して、異なるタイプのインスタンス化ごとに異なる ID を持つようにします。日付: 2023/7/25
質問者: 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
提出では、図に示すように、マルチプラットフォームのテスト結果を提供する必要があります。
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;
}
};
分析します:
Component
の静的メンバー関数component_type_id
を実装する必要があります。これは、次のコードからわかります。
class A : public Component <A>
{};
A::component_type_id ()
この質問では、各カスタム クラス タイプ (X であると想定) がComponent<X>
を継承し、 component_type_id()
を呼び出すと独自の一意の ID が返されることが必要です。他のタイプについても同様です。
問題を解決する前に、次の 1 つの知識ポイントを強調する必要があります。
C++ テンプレートは特定の型ではなく、インスタンス化後のものです (つまり、関数テンプレートは関数ではなく、クラス テンプレートの静的メンバー関数はクラスではありません)。テンプレートには属しませんが、特定の型に属します。インスタンス化後、コードの一部を使用して結論を示すことができます。
# 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
}
このコードは、テンプレートのインスタンス化後に静的データ メンバーが特定の型に属することを簡単に示します。 Test<void>::n
とTest<int>::n
同じ n ではなく、 Test<void>
とTest<int>
は同じ型ではありません (同じことが静的メンバー関数にも当てはまります)。
したがって、私たちのソリューションでは次のことを使用します。 さまざまなタイプがComponent
クラス テンプレートをインスタンス化します。これは、さまざまな静的メンバー関数でもあり、静的メンバー関数の静的部分も一意であり、最初に呼び出されたときにのみ初期化されます。
scope_guard
型05
日付: 2023/7/29
質問者: Da'Inihlus
これは、 scope_guard
型を実装する必要があります (つまり、任意の呼び出し可能な型を渡し、破棄中に同時にそれを呼び出すことがサポートされます)。
# 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
使用して型を消去します 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>...>;
std::atomic
初期化の06
日付: 2023/8/2
質問者: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
上記のコードは C++17 以降ではコンパイルできるが、C++17 より前ではコンパイルできない理由を説明してください。
std::atomic<int> n = 6
では、 6
とstd::atomic<int>
は同じ型ではないため (ただし、実際にはここにユーザー定義の変換シーケンスがあるため、 6
暗黙的に変換できると単純に考えることができます) )。
つまり、変換コンストラクターを呼び出します。
constexpr atomic ( T desired ) noexcept ;
変換コンストラクターは、ユーザー定義の変換シーケンスの一部としても使用されます。
6
変換コンストラクターを呼び出して一時的なアトミック オブジェクトを構築し、 n
直接初期化します。
std::atomic< int > n (std::atomic< int >( 6 ))
C++17 より前のバージョンでは、要件を満たしている場合、コンパイルする前にコピー/移動コンストラクターを検索して検出する必要があるのは当然です。しかし: