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
기능을 변경하지 않는 경우 실행을 중지해서는 안 됩니다(즉, 이를 활용하지 마십시오).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 | f2
operator |
호출하여 v의 모든 요소를 순회한 다음 v의 참조를 반환합니다.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
제약 조건 표현식에 노출된 적이 없더라도 아래에서 간략하게 소개하겠습니다.
require 표현식은 bool을 반환하는 함수와 같으며, U와 F는 std::regular_invocable의 실제 매개변수 목록에 유형으로 채워져 있습니다. 유형이 표현식을 만족하는 한, 그렇지 않으면 true를 반환합니다. , 이는 "제약조건이 충족되지 않음"이라고 불리는 false를 반환합니다. 제약 조건을 충족하지 않는 유형은 당연히 후속 코드를 실행하지 않습니다.
std::regular_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 함수를 각 요소에 한 번 적용합니다. 평소대로 v1으로 돌아갑니다.
템플릿을 사용하지 않는 경우 형식 매개변수 목록은 std::function을 사용하여 우리가 사용하는 함수를 포착해야 합니다.
범위의 각 멤버에 f를 적용하면 반환 값이 필요하지 않으며 범위의 요소 수정이 필요하므로 두 번째 매개 변수는 std::function<void(int&)>
입니다. 그리고 전달된 함수 f 를 수정하거나 복사할 필요가 없으므로 const 제한을 추가하는 것이 좋은 습관입니다.
마찬가지로, range for는 사용할 수 없지만 더 간단한 std::ranges::for_each(v1, f);
즉, 위와 같이 v1 범위의 각 요소에 함수 f를 한 번 적용합니다.
템플릿 사용 형식의 경우 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
위 코드의 두 가지 사용 예는 사용자 정의 리터럴의 기본 사용을 보여줍니다. 두 번째 단락인 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
입니다. 즉, 형식화하려는 규칙이 무엇인지를 나타내는 두 번째 매개변수는 형식화할 매개변수입니다. 공식 매개변수 패키지를 확장하세요. 두 번째 매개변수의 유형은 실제로 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
다음과 같이 온라인으로 컴파일된 세 가지 플랫폼의 스크린샷과 함께 코드를 제출하는 것이 가장 좋습니다.
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에서 작성되었습니다. Cookbook 에는 모든 형식을 지원하는 std::ranges::range
및 std::tuple
에 대한 전문화가 있습니다.
인쇄를 구현하는 것은 매우 간단합니다. 형식화된 문자열의 경우 첫 번째 형식 매개변수로 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 템플릿 전문화를 사용자 정의하려면 구문 분석 및 형식이라는 두 가지 기능을 제공해야 한다는 것입니다.
구문 분석은 형식 설명을 처리하고 관련 멤버 변수를 설정하는 데 사용됩니다. 이 질문에 대해서는 이 멤버 함수를 구현하는 데 어려움을 겪을 필요가 없습니다.
우리는 std::formatter<char>
의 구문 분석 기능을 상속하고 형식 기능을 독립적으로 구현하기로 선택했습니다. 여기에서 템플릿 전문화의 구문을 이해하지 못한다면 템플릿 전문화를 검토하세요.
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 );
}
};
또한 자리 표시자를 위한 단축 함수 템플릿으로 auto를 사용합니다. 형식 함수의 경우 첫 번째 매개 변수는 우리가 전달하는 사용자 정의 클래스이고 두 번째 매개 변수( ctx )는 std::format_to
출력 반복자에 전달하려는 형식 문자입니다. . 끈.
함수 본문에서 std::format_to()
호출 표현식의 결과를 직접 반환합니다. 이 함수는 반환 값에 대한 출력 반복자를 반환하고 반환 값 파생을 위해 자동 자리 표시자를 사용합니다.
함수 매개변수 중 ctx.out()
은 출력 반복자이고, 두 번째 매개변수는 std::string_view
또는 std::wstring_view
로 변환할 수 있는 합법적인 형식 문자열이며, 변환 결과는 상수 표현식과 Args입니다. 이 질문에서는 필요한 양식, 즉 {}/{}
을 작성합니다.
printf(%d,x)
사용하는 것처럼 두 매개변수를 {}
에 넣기를 원합니다. 마지막 두 매개변수는 " {}
에 채워야 하는 값"입니다. 포맷됩니다.
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를 반환해야 합니다. 다른 유형도 마찬가지입니다.
문제를 해결하기 전에 한 가지 지식 포인트를 강조해야 합니다.
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 이전 버전에서는 요구 사항을 충족하는 경우 복사/이동 생성자를 컴파일하기 전에 검색하고 감지해야 하는 것이 당연합니다. 하지만: