c++ - tour - ¿Cuál es el punto de una expresión de restricción en una función sin plantilla?
tipos de funciones en c++ (2)
[temp.constr.decl] dice que podemos restringir una plantilla o una función con una expresión de restricción .
Los declaradores [dcl.decl] nos dicen que, para las funciones, podemos agregar una cláusula final opcional requerida para restringirla, y el borrador estándar n4820 incluso ofrece estos ejemplos (aparentemente sin sentido):
void f1(int a) requires true;
auto f2(int a) -> bool requires true;
Entiendo que restringir una plantilla o un concepto es útil, pero no veo cómo estas restricciones son útiles para las funciones sin plantilla. ¿Cuál es el punto de restringir una función sin plantilla?
Así como un concepto considérese el siguiente ejemplo.
#include <iostream>
void f( long x ) requires ( sizeof( long ) == sizeof( int ) )
{
std::cout << "Bye " << x << ''/n'';
}
void f( long long x ) requires ( sizeof( long ) == sizeof( long long ) )
{
std::cout << "Hello " << x << ''/n'';
}
int main()
{
f( 0l );
}
Si
sizeof( long ) == sizeof( long long )
entonces la salida del programa será
Hello 0
De otra manera
Bye 0
Por ejemplo, puede utilizar este enfoque en una función que calcula el factorial para restringir el número de iteraciones de un bucle o para lanzar una excepción.
Aquí hay un programa demostrativo.
#include <iostream>
#include <stdexcept>
unsigned long factorial( unsigned long n ) noexcept( false )
requires ( sizeof( unsigned long ) == sizeof( unsigned int ) )
{
const unsigned long MAX_STEPS = 12;
if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." );
unsigned long f = 1;
for ( unsigned long i = 1; i < n; i++ ) f *= ( i + 1 );
return f;
}
unsigned long long factorial( unsigned long long n ) noexcept( false )
requires ( sizeof( unsigned long ) == sizeof( unsigned long long ) )
{
const unsigned long long MAX_STEPS = 20;
if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." );
unsigned long f = 1;
for ( unsigned long long i = 1; i < n; i++ ) f *= ( i + 1 );
return f;
}
int main()
{
unsigned long n = 20;
try
{
std::cout << factorial( n ) << ''/n'';
}
catch ( const std::out_of_range &ex )
{
std::cout << ex.what() << ''/n'';
}
}
Su salida puede ser cualquiera
2432902008176640000
o
Too big value.
Uno de los puntos principales de la restricción de las funciones que no son de plantilla es poder escribir restricciones en los miembros sin plantilla de las clases de plantilla. Por ejemplo, podría tener algún tipo como este:
template<typename T>
class value
{
public:
value(const T& t);
value(T&& t);
private:
T t_;
};
Ahora, quiere que el
value
sea copiable / movible desde
T
Pero en realidad, usted quiere que se pueda copiar / mover desde
T
solo
en la medida en que
T
sí pueda copiarse / moverse.
¿Entonces, cómo lo haces?
Pre-restricciones, necesitarías escribir un montón de piratería de meta-programación.
Tal vez usted haga estas plantillas de constructores, que requieren que el tipo
U
dado sea el mismo que
T
, además del requisito de copiar / mover.
O puede que tenga que escribir una clase base de la que herede, que tiene diferentes especializaciones basadas en la capacidad de copia / movilidad de
T
Post-restricciones, haces esto:
template<typename T>
class value
{
public:
value(const T& t) requires is_copy_constructible_v<T> : t_(t) {}
value(T&& t) requires is_move_constructible_v<T> : t_(std::move(t)) {}
private:
T t_;
};
No hay piratería. No aplique plantillas a funciones que no necesitan ser plantillas. Simplemente funciona, y es fácil para el usuario entender lo que está sucediendo.
Esto es especialmente importante para las funciones que no pueden ser plantillas. Para que un constructor se considere un constructor de copia o movimiento, no puede ser una plantilla. Lo mismo ocurre con los operadores de copia / movimiento de asignación. Pero tales cosas pueden tener restricciones.