01
оператора трубопровода02
Реализация пользовательского литерала _f
03
Реализуйте print
и специализируйте std::formatter
04
Измените данный шаблон класса так, чтобы он имел разные идентификаторы для каждого экземпляра типа.05
scope_guard
06
инициализации std::atomic
07
throw new MyException
08
руководства по построению array
09
с поиском имени10
Обход любых членов данных классаC++17
C++20
11
проблем с emplace_back()
12
make_vector()
13
return std::move
14
Изменить объекты, объявленные в пространстве имен, с помощью специальных методов.15
шаблонов выражений16
макросов для создания шаблонов передаточных функцийПоказ домашнего задания Лютора.
Отправка запроса на запрос не должна изменять текущий README
. Отправьте задание в src群友提交
. Например, если вы хотите отправить первое задание:
Вам следует создать свой собственный файл .md
или .cpp
в src群友提交第01题
Имя файла должно быть названо в честь вашего собственного идентификатора коммуникационной группы (или имени пользователя 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. будет изменен. v | f2
вызывает operator |
Оператор | использует f2 для обхода каждого элемента 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 как типы. Пока U и F как типы удовлетворяют выражению, оно возвращает 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 , поэтому хорошей привычкой является добавление константных ограничений.
Точно так же мы можем использовать не диапазон, а более простой std::ranges::for_each(v1, f);
то есть применить функцию f один раз к каждому элементу в диапазоне v1, как указано выше.
В качестве формы использования шаблонов мы можем использовать шаблон функции сокращения 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
Два примера использования приведенного выше кода показывают базовое использование наших пользовательских литералов, обратите особое внимание на второй абзац, возвращаемое значение . Если вы хотите назвать это как "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 , вам необходимо предоставить две функции: parse и format .
синтаксический анализ используется для обработки описаний формата и установки связанных переменных-членов. Для этого вопроса нам не нужно беспокоиться о реализации этой функции-члена;
Мы решили наследовать функцию синтаксического анализа 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
, а результатом преобразования является константное выражение и аргументы. В этом вопросе мы заполняем нужную нам форму, а именно {}/{}
.
Мы хотим, чтобы эти два параметра были вставлены в {}
, точно так же, как мы используем printf(%d,x)
; последние два параметра — это «значения, которые необходимо вставить в {}
», то есть параметры для быть отформатирован.
04
Измените данный шаблон класса так, чтобы он имел разные идентификаторы для каждого экземпляра типа. Дата: 2023/7/25
Вопрос: Адтил
# 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_type_id
компонента Component
. Это известно из приведенного кода:
class A : public Component <A>
{};
A::component_type_id ()
Вопрос требует, чтобы каждый тип пользовательского класса (предположительно X) наследовал Component<X>
, а вызов component_type_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
, которые также являются разными статическими функциями-членами. Статические части статических функций-членов также уникальны и инициализируются только при первом вызове. Это не так.
05
scope_guard
Дата: 2023/7/29
Вопрос: Да'Инихлус
Требуется реализовать 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>...>;
06
инициализации std::atomic
Дата: 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 естественно, что конструктор копирования/перемещения должен быть найден и обнаружен до того, как его можно будет скомпилировать, если он соответствует требованиям. но: