01
the pipeline operator02
Implement custom literal _f
03
Implement print
and specialize std::formatter
04
Modify the given class template so that it has a different ID for each different type instantiation05
scope_guard
type06
of std::atomic
initialization07
throw new MyException
08
of array
derivation guide09
search problem10
Traverse any class data membersC++17
writing methodC++20
writing method11
Problems with emplace_back()
12
make_vector()
13
return std::move
14
Modify objects declared in the namespace in special methods15
expression templates16
Macros for making transfer function templatesLuthor's homework display.
Submitting a PR should not change the current README
. Please submit the job to src群友提交
. For example, if you want to submit the first job:
You should create your own .md
or .cpp
file in src群友提交第01题
The file name should be named after your own communication group ID (or GitHub username, so you can easily find yourself) .
The general requirements for answering questions are as follows (pay attention to the additional requirements for the questions):
main
function, you must not stop it from running (meaning don't take advantage of it).01
the pipeline operator Date: 2023/7/21
Questioner: mq白
Given the code:
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
Answered by: 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;
}
It's normal, no problem.
Answered by: 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;
}
Comment: If I have nothing to do, I will write more overloads and frame them.
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;
}
Without using templates :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
Instead of using a scoped for
, use the C++20 shorthand function template:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
The paradigms of various other answers are nothing more than these changes, there is no need to write them again.
Obviously we need to overload the pipeline operator |. According to our calling form v | f2 | f
, this chain of calls, and based on the given running results, we can know that the overloaded function should return a reference to v, and v will be modified. v | f2
calls operator |
. Operator | uses f2 to traverse every element in v, then returns the reference of v, and then | f.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
If you have never been exposed to constraint expressions, it doesn’t matter. We will briefly introduce them below.
The requires expression is like a function that returns bool, and U and F are filled in the actual parameter list of std::regular_invocable as types. As long as U and F as types satisfy the expression, it returns true; if it does not, it returns false, which is called is "constraint not satisfied". Types that do not satisfy constraints will naturally not execute subsequent code.
As for std::regular_invocable, we can simply think of it as a pair of each value of type U. Whether we can call function F, that is, call std::invoke
.
This is equivalent to us imagining the runtime at compile time and imagining whether U can execute F at runtime. If so, it satisfies the constraints.
The function body is extremely simple
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
The range expression for (auto& i : v1)
is like for(auto i=v.begin();i!=v.end();++i){f(*i)}
: we have vector (range ) applies the f function once to each element in it. Returns to v1 as usual.
If we do not use templates, our formal parameter list must use std::function to catch the function we use:
Applying f to each member of the range does not require a return value and requires modification of the elements in the range, so the second parameter is std::function<void(int&)>
. And we don’t need to modify or copy the function f passed in, so it is a good habit to add const restrictions.
Similarly, we can not use range for but the simpler std::ranges::for_each(v1, f);
that is, apply function f once to each element in range v1 as above.
For the form of using templates, we can use the C++20 abbreviation function template; in short, the auto placeholder in the function parameter list will append a dummy template parameter to the template parameter list. The initial template form can be written as
std::vector< int >& operator |( auto & v1, const auto & f)
It is the same as the original form.
02
Implement custom literal _f
Date: 2023/7/22
Questioner: mq白
Given the code:
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
is the input and determines
Answered by: 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...)); };
}
We need to use C++11 user-defined literals, and ""_f
is exactly the user-defined literal.
However, the formal parameter list of the literal operator (the function called by the user-defined literal is called the literal operator) has some restrictions. What we need is a formal parameter list such as const char *, std::size_t
, which happens to be this is allowed; the return type of a literal operator needs to be customized, and this type needs to overload operator()
internally to meet the above requirements for literals to be called like functions.
Let’s go step by step:
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
The two usage examples of the above code show the basic use of our user-defined literals, pay special attention to the second paragraph, return value . If you want to call it like "xxx"_f(xxx)
, you have to do something with the return type.
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
The above simple code perfectly completes the calling form we need, then it is time to complete the functions required by the question. The simplest way is to use the C++20 format library directly for formatting.
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
itself is very simple. It is just used to construct impl::Helper
object from the incoming parameters (format string) and length and then return it. The Helper
type uses a string_view
as a data member to store the format string for later formatting.
The focus is only on operator()
. It is a variable parameter template, used to receive any type and number of parameters we pass in, and then return a formatted string.
What is used here is std::vformat
for formatting. Its first parameter is the format string, that is, what rules we want to format according to; the second parameter is the parameter to be formatted, but we There is no way to directly expand the formal parameter package. The type of its second parameter is actually std::format_args
. We must use std::make_format_args
function to pass in our parameters. It will return std::format_args
type. In fact, it is equivalent to conversion, which is reasonable.
But obviously the standard answer is not like this, and it can be simplified by simply letting ""_f
return a lambda expression.
03
Implement print
and specialize std::formatter
Date: 2023/7/24
Questioner: mq白
Implementing a print
, if you did the previous assignment, I believe this is simple. The required calling form is:
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
Given a custom type Frace
, request support
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
Result-oriented programming, the use of macros, etc. are prohibited, with a maximum of B
(referring to evaluation). This assignment mainly examines and learns format
library.
Tip: std::formatter
It is best to submit code with screenshots of three platforms compiled online, such as:
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...));
}
We simply support the form required by the question and specialize std::formatter
. If we want to support formatting such as {:6}
, it is obviously not possible. This involves more operations. Simple specializations and forms supported by std::formatter
can be found in the documentation . Some complex specializations have been written by up before. In the Cookbook , there are specializations for std::ranges::range
and std::tuple
, supporting all forms.
Implementing a print is very simple. We just need to follow the idea of the second question. For a formatted string, use std::string_view as the first formal parameter. In addition, if any parameters and number are needed, just use the formal parameter package. .
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
Calling vformat
in this way returns a string, which can be output directly using cout.
Regarding custom std::formatter
specialization, what we need to know is: if you want to customize std::formatter template specialization, you need to provide two functions, parse and format .
parse is used to process format descriptions and set related member variables. For this question, we do not need to go to the trouble of implementing this member function;
We choose to inherit the parse function of std::formatter<char>
and implement the format function independently. If you don't understand the syntax of template specializations here, review Template Specializations.
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 );
}
};
We also use auto as a shorthand function template for placeholders. For the format function, the first parameter is the custom class we pass, and the second parameter ( ctx ) is the format character we want to pass to std::format_to
output iterator. string.
In the function body, we directly return the result of std::format_to()
call expression. This function returns the output iterator; for the return value, we use the auto placeholder for return value derivation.
Among the function parameters, ctx.out()
is the output iterator, the second parameter is a legal format string that can be converted to std::string_view
or std::wstring_view
, and the conversion result is a constant expression and Args. In this question, we fill in the form we need, namely {}/{}
.
We want the two parameters to be stuffed into {}
, just like we use printf(%d,x)
; the last two parameters are the "values that need to be stuffed into {}
", that is, the parameters to be formatted.
04
Modify the given class template so that it has a different ID for each different type instantiation Date: 2023/7/25
Questioner: 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
Submission should provide multi-platform test results, as shown in the figure:
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;
}
};
analyze:
We need to implement the static member function component_type_id
of Component
. This is known from the code given:
class A : public Component <A>
{};
A::component_type_id ()
The question requires that each custom class type (assumed to be X) inherits Component<X>
, and calling component_type_id()
returns its own unique ID. The same goes for other types.
Before solving the problem, we need to emphasize one knowledge point:
C++ templates are not specific types, they are after instantiation (that is, function templates are not functions, and class templates are not classes ). Static members or static member functions of class templates do not belong to templates, but to specific types after instantiation . We You can use a piece of code to demonstrate the conclusion:
# 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
}
This code easily shows that static data members belong to the specific type after instantiation of the template . Test<void>::n
and Test<int>::n
are not the same n, and Test<void>
and Test<int>
are not the same type (the same applies to static member functions).
So our solution uses: different types of instantiated Component
class templates are also different static member functions. The static parts in the static member functions are also unique and will only be initialized when called for the first time. It won't.
05
scope_guard
type Date: 2023/7/29
Questioner: Da'Inihlus
It is required to implement the scope_guard
type (that is, it supports passing in any callable type and calling it at the same time during destruction).
# 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
and erase the type 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
of std::atomic
initialization Date: 2023/8/2
Questioner: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
Explain why the above code can be compiled after C++17 but not before C++17?
In std::atomic<int> n = 6
, since 6
and std::atomic<int>
are not the same type (but there is actually a user-defined conversion sequence here, you can simply think that 6
can be implicitly converted).
That is, calling the conversion constructor:
constexpr atomic ( T desired ) noexcept ;
Conversion constructors are also used as part of a user-defined conversion sequence
6
will call the conversion constructor to construct a temporary atomic object to directly initialize n
, that is
std::atomic< int > n (std::atomic< int >( 6 ))
In versions before C++17 , it is natural that the copy/move constructor should be searched and detected before it can be compiled if it meets the requirements. but: