01 o operador de pipeline02 Implementar literal personalizado _f03 Implementar print e especializar std::formatter04 Modifique o modelo de classe fornecido para que ele tenha um ID diferente para cada tipo de instanciação diferente05 tipo scope_guard06 da inicialização std::atomic07 throw new MyException08 de guia de derivação array0910 Percorra quaisquer membros de dados da classeC++17C++2011 Problemas com emplace_back()12 make_vector()13 return std::move14 Modifique objetos declarados no namespace em métodos especiais15 modelos de expressão16 Macros para criar modelos de funções de transferênciaExibição de lição de casa de Luthor.
O envio de um PR não deve alterar o README atual. Envie o trabalho para src群友提交. Por exemplo, se você deseja enviar o primeiro trabalho:
Você deve criar seu próprio arquivo .md ou .cpp em src群友提交第01题O nome do arquivo deve ser nomeado após o seu próprio ID de grupo de comunicação (ou nome de usuário do GitHub, para que você possa se encontrar facilmente) .
Os requisitos gerais para responder às perguntas são os seguintes (preste atenção aos requisitos adicionais para as perguntas):
main , não deverá interromper sua execução (ou seja, não tirar vantagem dela).01 o operador de pipeline Data: 2023/7/21 Questionador: mq白
Dado o código:
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
Respondido por: 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;
}É normal, não há problema.
Respondido por: 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;
}Comentário: Se não tiver nada para fazer, escreverei mais sobrecargas e as enquadrarei.
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;
}Sem usar modelos :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
} Em vez de usar um escopo for , use o modelo de função abreviada do C++20:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}Os paradigmas de várias outras respostas nada mais são do que essas mudanças, não há necessidade de escrevê-los novamente.
Obviamente, precisamos sobrecarregar o operador de pipeline |. De acordo com nosso formulário de chamada v | f2 | f , esta cadeia de chamadas, e com base nos resultados de execução fornecidos, podemos saber que a função sobrecarregada deve retornar uma referência a v e v. será modificado. v | f2 chama operator | operador | usa f2 para percorrer todos os elementos em v, depois retorna a referência de v e, em seguida, f.
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束Se você nunca foi exposto a expressões de restrição, não importa. Iremos apresentá-las brevemente a seguir.
A expressão requer é como uma função que retorna bool, e U e F são preenchidos na lista de parâmetros real de std::regular_invocable como tipos Desde que U e F como tipos satisfaçam a expressão, ela retornará true se não o fizer. , ele retorna falso, que é chamado de "restrição não satisfeita". Os tipos que não satisfazem as restrições naturalmente não executarão o código subsequente.
Quanto a std::regular_invocable, podemos simplesmente pensar nele como um par de cada valor do tipo U. Se podemos chamar a função F, ou seja, chamar std::invoke .
Isso equivale a imaginarmos o tempo de execução em tempo de compilação e imaginarmos se U pode executar F em tempo de execução. Se sim, satisfaz as restrições.
O corpo da função é extremamente simples
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
} A expressão de intervalo for (auto& i : v1) é como for(auto i=v.begin();i!=v.end();++i){f(*i)} : temos vetor (range ) aplica a função f uma vez a cada elemento nela. Retorna para v1 normalmente.
Se não usarmos modelos, nossa lista formal de parâmetros deverá usar std::function para capturar a função que usamos:
Aplicar f a cada membro do intervalo não requer um valor de retorno e requer modificação dos elementos do intervalo, portanto, o segundo parâmetro é std::function<void(int&)> . E não precisamos modificar ou copiar a função f passada, por isso é um bom hábito adicionar restrições const .
Da mesma forma, não podemos usar range for, mas o mais simples std::ranges::for_each(v1, f); isto é, aplicar a função f uma vez a cada elemento no intervalo v1 como acima.
Para a forma de uso de modelos, podemos usar o modelo de função de abreviatura C++20, em resumo, o espaço reservado automático na lista de parâmetros de função anexará um parâmetro de modelo fictício à lista de parâmetros de modelo; O formulário do modelo inicial pode ser escrito como
std::vector< int >& operator |( auto & v1, const auto & f) É igual ao formato original.
02 Implementar literal personalizado _f Data: 2023/7/22 Questionador: mq白
Dado o código:
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 é a entrada e determina
Respondido por: 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...)); };
} Precisamos usar literais definidos pelo usuário C++ 11 e ""_f é exatamente o literal definido pelo usuário.
No entanto, a lista formal de parâmetros do operador literal (a função chamada pelo literal definido pelo usuário é chamada de operador literal) tem algumas restrições. O que precisamos é de uma lista formal de parâmetros como const char *, std::size_t , que. acontece que isso é permitido; o tipo de retorno de um operador literal precisa ser personalizado e esse tipo precisa sobrecarregar operator() internamente para atender aos requisitos acima para que literais sejam chamados como funções.
Vamos passo a passo:
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 Os dois exemplos de uso do código acima mostram o uso básico de nossos literais definidos pelo usuário, preste atenção especial ao segundo parágrafo, valor de retorno . Se você quiser chamá-lo como "xxx"_f(xxx) , precisará fazer algo com o tipo de retorno.
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 ); //打印 1O código simples acima completa perfeitamente o formulário de chamada que precisamos, então é hora de completar as funções exigidas pela pergunta. A maneira mais simples é usar a biblioteca de formato C++20 diretamente para formatação.
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 em si é muito simples. Ele é usado apenas para construir impl::Helper a partir dos parâmetros de entrada (string de formato) e comprimento e então retorná-lo. O tipo Helper usa string_view como membro de dados para armazenar a string de formato para formatação posterior.
O foco está apenas em operator() . É um modelo de parâmetro variável, usado para receber qualquer tipo e número de parâmetros que passamos e depois retornar uma string formatada.
O que é usado aqui é std::vformat para formatação. Seu primeiro parâmetro é a string de formato, ou seja, quais regras queremos formatar de acordo com o segundo parâmetro é o parâmetro a ser formatado, mas não há como formatar diretamente; expanda o pacote de parâmetros formais. O tipo de seu segundo parâmetro é na verdade std::format_args . Devemos usar std::make_format_args para passar nossos parâmetros. Ela retornará std::format_args Na verdade, é equivalente à conversão, o que é razoável.
Mas obviamente a resposta padrão não é assim e pode ser simplificada simplesmente deixando ""_f retornar uma expressão lambda.
03 Implementar print e especializar std::formatter Data: 2023/7/24 Questionador: mq白
Implementando um print , se você fez a tarefa anterior, acredito que seja simples. O formulário de chamada necessário é:
print (格式字符串,任意类型和个数的符合格式字符串要求的参数) struct Frac {
int a, b;
}; Dado um tipo personalizado Frace , solicite suporte
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10 1/10
É proibida a programação orientada para resultados, o uso de macros, etc., com um máximo de B (referente à avaliação). Esta tarefa examina e aprende principalmente format .
Dica: std::formatter
É melhor enviar código com capturas de tela de três plataformas compiladas online, como:

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...));
} Simplesmente apoiamos o formulário exigido pela pergunta e nos especializamos std::formatter . Se quisermos oferecer suporte a formatação como {:6} , obviamente isso não é possível. Especializações e formulários simples suportados por std::formatter podem ser encontrados na documentação . Algumas especializações complexas foram escritas por up antes. No Cookbook , existem especializações para std::ranges::range e std::tuple , suportando todos os formulários.
Implementar um print é muito simples. Basta seguir a ideia da segunda pergunta. Para uma string formatada, use std::string_view como primeiro parâmetro formal. o pacote de parâmetros formais.
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
} Chamar vformat dessa maneira retorna uma string, que pode ser gerada diretamente usando cout.
Em relação à especialização std::formatter personalizada, o que precisamos saber é: se você deseja personalizar a especialização do modelo std::formatter , você precisa fornecer duas funções, parse e format .
parse é usado para processar descrições de formato e definir variáveis de membro relacionadas. Para esta questão, não precisamos nos dar ao trabalho de implementar esta função de membro;
Optamos por herdar a função de análise de std::formatter<char> e implementar a função de formato de forma independente. Se você não entende a sintaxe das especializações de modelo aqui, revise Especializações de modelo.
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 );
}
}; Também usamos auto como um modelo de função abreviada para espaços reservados. Para a função de formato , o primeiro parâmetro é a classe personalizada que passamos e o segundo parâmetro ( ctx ) é o caractere de formato que queremos passar para std::format_to . corda.
No corpo da função, retornamos diretamente o resultado std::format_to() . Esta função retorna o iterador de saída para o valor de retorno, usamos o espaço reservado automático para derivação do valor de retorno.
Entre os parâmetros de função, ctx.out() é o iterador de saída, o segundo parâmetro é uma string de formato legal que pode ser convertida em std::string_view ou std::wstring_view , e o resultado da conversão é uma expressão constante e Args. Nesta questão, preenchemos o formulário que precisamos, ou seja, {}/{} .
Queremos que os dois parâmetros sejam inseridos em {} , assim como usamos printf(%d,x) ; os dois últimos parâmetros são os "valores que precisam ser inseridos em {} ", ou seja, os parâmetros para serem inseridos em {} ; ser formatado.
04 Modifique o modelo de classe fornecido para que ele tenha um ID diferente para cada tipo de instanciação diferente Data: 2023/7/25 Questionador: 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
O envio deve fornecer resultados de testes multiplataforma, conforme mostrado na figura:

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;
}
};analisar:
Precisamos implementar a função de membro estático component_type_id de Component . Isso é conhecido pelo código fornecido:
class A : public Component <A>
{};
A::component_type_id () A questão exige que cada tipo de classe personalizada (supostamente X) herde Component<X> e chamar component_type_id() retorne seu próprio ID exclusivo. O mesmo vale para outros tipos.
Antes de resolver o problema, precisamos enfatizar um ponto de conhecimento:
Os modelos C++ não são tipos específicos, eles vêm após a instanciação (ou seja, os modelos de função não são funções e os modelos de classe não são classes Membros estáticos ou funções de membro estático de modelos de classe não pertencem a modelos, mas a tipos específicos após a instanciação ). . Nós Você pode usar um trecho de código para demonstrar a conclusão:
# 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
} Este código mostra facilmente que os membros de dados estáticos pertencem ao tipo específico após a instanciação do modelo . Test<void>::n e Test<int>::n não são iguais n, e Test<void> e Test<int> não são do mesmo tipo (o mesmo se aplica a funções de membro estáticas).
Portanto, nossa solução usa: diferentes tipos de modelos de classe de Component instanciados também são funções de membro estáticas diferentes. As partes estáticas nas funções de membro estáticas também são exclusivas e só serão inicializadas quando chamadas pela primeira vez.
05 tipo scope_guard Data: 2023/7/29 Questionador: Da'Inihlus
É necessário implementar o tipo scope_guard (ou seja, ele suporta a passagem de qualquer tipo que pode ser chamado e a chamada ao mesmo tempo durante a destruição).
# 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 e apague o tipo 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 da inicialização std::atomic Data: 2023/8/2 Questionador: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}Explique por que o código acima pode ser compilado depois do C++ 17, mas não antes do C++ 17?

Em std::atomic<int> n = 6 , como 6 e std::atomic<int> não são do mesmo tipo (mas na verdade há uma sequência de conversão definida pelo usuário aqui, você pode simplesmente pensar que 6 pode ser convertido implicitamente ).
Ou seja, chamando o construtor de conversão:
constexpr atomic ( T desired ) noexcept ;Os construtores de conversão também são usados como parte de uma sequência de conversão definida pelo usuário
6 chamará o construtor de conversão para construir um objeto atômico temporário para inicializar diretamente n , ou seja
std::atomic< int > n (std::atomic< int >( 6 ))Em versões anteriores ao C++17 , é natural que o construtor copiar/mover seja pesquisado e detectado antes de poder ser compilado, se atender aos requisitos. mas: