01
el operador del oleoducto02
Implementar literal personalizado _f
03
Implementar print
y especializar std::formatter
04
Modifique la plantilla de clase dada para que tenga una ID diferente para cada tipo de instanciación diferente05
el tipo scope_guard
06
de std::atomic
07
throw new MyException
08
de guía de derivación array
.09
10
Atraviesa cualquier miembro de datos de claseC++17
C++20
11
Problemas con emplace_back()
12
make_vector()
13
return std::move
14
Modificar objetos declarados en el espacio de nombres en métodos especiales15
plantillas de expresión16
macros para crear plantillas de funciones de transferenciaExhibición de tareas de Luthor.
Enviar un PR no debería cambiar el README
actual. Envíe el trabajo a src群友提交
. Por ejemplo, si desea enviar el primer trabajo:
Debe crear su propio archivo .md
o .cpp
en src群友提交第01题
El nombre del archivo debe tener el nombre de su propio ID de grupo de comunicación (o nombre de usuario de GitHub, para que pueda encontrarlo fácilmente) .
Los requisitos generales para responder preguntas son los siguientes (preste atención a los requisitos adicionales para las preguntas):
main
, no debe detener su ejecución (es decir, no aprovecharla).01
el operador del oleoducto Fecha: 2023/7/21
Interlocutor: mq白
Dado el 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;
}
Es normal, no hay 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;
}
Comentario: Si no tengo nada que hacer, escribiré más sobrecargas y las enmarcaré.
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;
}
Sin utilizar plantillas :
std::vector< int >& operator |(std::vector< int >& v1, const std::function< void ( int &)>& f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
En lugar de utilizar un ámbito for
, utilice la plantilla de función abreviada de C++20:
std::vector< int >& operator |( auto & v1, const auto & f) {
std::ranges::for_each (v1, f);
return v1;
}
Los paradigmas de varias otras respuestas no son más que estos cambios, por lo que no es necesario volver a escribirlos.
Obviamente necesitamos sobrecargar el operador de canalización |. De acuerdo con nuestra forma de llamada v | f2 | f
, esta cadena de llamadas, y según los resultados de ejecución dados, podemos saber que la función sobrecargada debería devolver una referencia a v y v. será modificado. v | f2
llama operator |
El operador | usa f2 para recorrer cada elemento en v, luego devuelve la referencia de v, y luego |
template < typename U, typename F>
requires std::regular_invocable<F, U&> //我们可以认为对模板形参U,F满足std::regular_invocable的约束
Si nunca ha estado expuesto a expresiones de restricción, no importa. Las presentaremos brevemente a continuación.
La expresión require es como una función que devuelve bool, y U y F se completan en la lista de parámetros real de std::regular_invocable como tipos Siempre que U y F como tipos satisfagan la expresión, devuelve verdadero; , devuelve falso, lo que se denomina "restricción no satisfecha". Los tipos que no satisfacen las restricciones, naturalmente, no ejecutarán el código posterior.
En cuanto a std::regular_invocable, simplemente podemos pensar en él como un par de cada valor de tipo U. Si podemos llamar a la función F, es decir, llamar std::invoke
.
Esto equivale a que imaginemos el tiempo de ejecución en tiempo de compilación e imaginemos si U puede ejecutar F en tiempo de ejecución. Si es así, satisface las restricciones.
El cuerpo de la función es extremadamente simple.
std::vector<U>& operator |(std::vector<U>& v1, const F f) {
for ( auto & i : v1) {
f (i);
}
return v1;
}
La expresión de rango for (auto& i : v1)
es como for(auto i=v.begin();i!=v.end();++i){f(*i)}
: tenemos vector (range ) aplica la función f una vez a cada elemento que contiene. Vuelve a la v1 como de costumbre.
Si no usamos plantillas, nuestra lista de parámetros formales debe usar std::function para capturar la función que usamos:
Aplicar f a cada miembro del rango no requiere un valor de retorno y requiere la modificación de los elementos en el rango, por lo que el segundo parámetro es std::function<void(int&)>
. Y no necesitamos modificar ni copiar la función f pasada, por lo que es un buen hábito agregar restricciones constantes .
De manera similar, no podemos usar range for sino el más simple std::ranges::for_each(v1, f);
es decir, aplicar la función f una vez a cada elemento en el rango v1 como se indicó anteriormente.
Para la forma de usar plantillas, podemos usar la plantilla de función abreviada de C++ 20. En resumen, el marcador de posición automático en la lista de parámetros de función agregará un parámetro de plantilla ficticio a la lista de parámetros de plantilla. El formulario de plantilla inicial se puede escribir como
std::vector< int >& operator |( auto & v1, const auto & f)
Es el mismo que el formulario original.
02
Implementar literal personalizado _f
Fecha: 2023/7/22
Interlocutor: mq白
Dado el 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
es la entrada y 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...)); };
}
Necesitamos usar literales definidos por el usuario de C++ 11, y ""_f
es exactamente el literal definido por el usuario.
Sin embargo, la lista de parámetros formales del operador literal (la función llamada por el literal definido por el usuario se llama operador literal) tiene algunas restricciones. Lo que necesitamos es una lista de parámetros formales como const char *, std::size_t
, que. Resulta que esto está permitido; el tipo de retorno de un operador literal debe personalizarse, y este tipo debe sobrecargar operator()
internamente para cumplir con los requisitos anteriores para que los literales se llamen funciones similares.
Vayamos paso a paso:
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
Los dos ejemplos de uso del código anterior muestran el uso básico de nuestros literales definidos por el usuario. Preste especial atención al segundo párrafo, valor de retorno . Si desea llamarlo como "xxx"_f(xxx)
, debe hacer algo con el 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
El código simple anterior completa perfectamente el formulario de llamada que necesitamos, entonces es el momento de completar las funciones requeridas por la pregunta. La forma más sencilla es utilizar la biblioteca de formato C++ 20 directamente para formatear.
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
en sí es muy simple. Simplemente se usa para construir impl::Helper
a partir de los parámetros entrantes (cadena de formato) y la longitud y luego devolverlo. El tipo Helper
utiliza string_view
como miembro de datos para almacenar la cadena de formato para su posterior formateo.
La atención se centra únicamente en operator()
. Es una plantilla de parámetros variables, que se utiliza para recibir cualquier tipo y cantidad de parámetros que pasemos y luego devolver una cadena formateada.
Lo que se usa aquí es std::vformat
para formatear. Su primer parámetro es la cadena de formato, es decir, según las reglas que queremos formatear, el segundo parámetro es el parámetro que se va a formatear, pero no hay forma de hacerlo directamente; expanda el paquete de parámetros formales. El tipo de su segundo parámetro es en realidad std::format_args
. Debemos usar std::make_format_args
para pasar nuestros parámetros. Devolverá std::format_args
. De hecho, es equivalente a la conversión, lo cual es razonable.
Pero, obviamente, la respuesta estándar no es así y se puede simplificar simplemente dejando que ""_f
devuelva una expresión lambda.
03
Implementar print
y especializar std::formatter
Fecha: 2023/7/24
Interlocutor: mq白
Implementar una print
, si hiciste la tarea anterior, creo que es simple. El formulario de llamada requerido es:
print (格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
int a, b;
};
Dado un tipo personalizado Frace
, solicite soporte
Frac f{ 1 , 10 };
print ( " {} " , f); // 结果为1/10
1/10
Está prohibida la programación orientada a resultados, el uso de macros, etc., con un máximo de B
(refiriéndose a la evaluación. Esta tarea examina y aprende principalmente format
).
Consejo: std::formatter
Lo mejor es enviar código con capturas de pantalla de tres plataformas compiladas en línea, como por ejemplo:
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...));
}
Simplemente admitimos el formulario requerido por la pregunta y nos especializamos std::formatter
. Si queremos admitir formatos como {:6}
, obviamente no es posible. Las especializaciones y formularios simples admitidos por std::formatter
se pueden encontrar en la documentación . Algunas especializaciones complejas han sido escritas anteriormente en Cookbook , hay especializaciones para std::ranges::range
y std::tuple
, que admiten todas las formas.
Implementar una impresión es muy simple. Solo necesitamos seguir la idea de la segunda pregunta. Para una cadena formateada, use std::string_view como primer parámetro formal. Además, si se necesitan parámetros y números, simplemente use. el paquete de parámetros formales.
void print (std::string_view fmt, auto &&...args){
std::cout << std::vformat (fmt, std::make_format_args (args...));
}
Llamar vformat
de esta manera devuelve una cadena, que se puede generar directamente usando cout.
En cuanto a la personalización de la especialización std::formatter
, lo que necesitamos saber es: si desea personalizar la especialización de la plantilla std::formatter , debe proporcionar dos funciones, analizar y formatear .
parse se utiliza para procesar descripciones de formato y establecer variables miembro relacionadas. Para esta pregunta, no necesitamos tomarnos la molestia de implementar esta función miembro;
Elegimos heredar la función de análisis de std::formatter<char>
e implementar la función de formato de forma independiente. Si no comprende la sintaxis de las especializaciones de plantillas aquí, revise Especializaciones de plantillas.
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 );
}
};
También usamos auto como plantilla de función abreviada para marcadores de posición. Para la función de formato , el primer parámetro es la clase personalizada que pasamos y el segundo parámetro ( ctx ) es el carácter de formato que queremos pasar al iterador de salida std::format_to
. cadena.
En el cuerpo de la función, devolvemos directamente el resultado de std::format_to()
. Esta función devuelve el iterador de salida para el valor de retorno, utilizamos el marcador de posición automático para la derivación del valor de retorno.
Entre los parámetros de la función, ctx.out()
es el iterador de salida, el segundo parámetro es una cadena de formato legal que se puede convertir a std::string_view
o std::wstring_view
, y el resultado de la conversión es una expresión constante y Args. En esta pregunta, completamos el formulario que necesitamos, a saber, {}/{}
.
Queremos que los dos parámetros se incluyan en {}
, tal como usamos printf(%d,x)
; los dos últimos parámetros son los "valores que deben incluirse en {}
", es decir, los parámetros para ser formateado.
04
Modifique la plantilla de clase dada para que tenga una ID diferente para cada tipo de instanciación diferente Fecha: 2023/7/25
Interlocutor: 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
El envío debe proporcionar resultados de pruebas multiplataforma, como se muestra en la 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;
}
};
analizar:
Necesitamos implementar la función miembro estática component_type_id
de Component
. Esto se sabe por el código proporcionado:
class A : public Component <A>
{};
A::component_type_id ()
La pregunta requiere que cada tipo de clase personalizada (que se supone que es X) herede Component<X>
y llamar component_type_id()
devuelva su propia ID única. Lo mismo ocurre con otros tipos.
Antes de resolver el problema, debemos enfatizar un punto de conocimiento:
Las plantillas de C ++ no son tipos específicos, son posteriores a la creación de instancias (es decir, las plantillas de funciones no son funciones y las plantillas de clases no son clases ). Los miembros estáticos o las funciones de miembros estáticos de las plantillas de clases no pertenecen a las plantillas, sino que pertenecen al tipo específico. después de la creación de instancias , podemos usar un fragmento de código para demostrar la conclusión:
# 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 muestra fácilmente que los miembros de datos estáticos pertenecen al tipo específico después de la creación de instancias de la plantilla . Test<void>::n
y Test<int>::n
no son lo mismo n, y Test<void>
y Test<int>
no son del mismo tipo (lo mismo se aplica a las funciones miembro estáticas).
Entonces, nuestra solución utiliza: diferentes tipos de instancias de plantillas de clases Component
, que también son diferentes funciones miembro estáticas. Las partes estáticas en las funciones miembro estáticas también son únicas y solo se inicializarán cuando se llamen por primera vez.
05
el tipo scope_guard
Fecha: 2023/7/29
Interlocutor: Da'Inihlus
Se requiere implementar el tipo scope_guard
(es decir, admite pasar cualquier tipo invocable y llamarlo al mismo tiempo durante la destrucción).
# 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
y borre el 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
de std::atomic
Fecha: 2023/8/2
Interlocutor: mq白
# include < iostream >
# include < atomic >
int main () {
std::atomic< int > n = 6 ;
std::cout << n << ' n ' ;
}
Explique por qué el código anterior se puede compilar después de C++ 17 pero no antes de C++ 17.
En std::atomic<int> n = 6
, dado que 6
y std::atomic<int>
no son del mismo tipo (pero en realidad hay una secuencia de conversión definida por el usuario aquí, simplemente puede pensar que 6
se puede convertir implícitamente ).
Es decir, llamando al constructor de conversión:
constexpr atomic ( T desired ) noexcept ;
Los constructores de conversión también se utilizan como parte de secuencias de conversión definidas por el usuario.
6
llamará al constructor de conversión para construir un objeto atómico temporal para inicializar directamente n
, es decir
std::atomic< int > n (std::atomic< int >( 6 ))
En versiones anteriores a C++ 17 , es natural que el constructor de copiar/mover se busque y detecte antes de poder compilarlo si cumple con los requisitos. pero: