01
o operador de pipeline02
Implementar literal personalizado _f
03
Implementar print
e especializar std::formatter
04
Modifique o modelo de classe fornecido para que ele tenha um ID diferente para cada tipo de instanciação diferente05
tipo scope_guard
06
da inicialização std::atomic
07
throw new MyException
08
de guia de derivação array
09
10
Percorra quaisquer membros de dados da classeC++17
C++20
11
Problemas com emplace_back()
12
make_vector()
13
return std::move
14
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 ); //打印 1
O 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: