Conceptos (C++)

De Wikipedia, la enciclopedia libre

Los conceptos permiten al desarrollador expresar las restricciones de tipos de un componente plantillado, de modo que el compilador puede comprender la intención del desarrollador y de ese modo señalar el error de manera clara en el punto donde se comete. De este modo los conceptos se convierten en una manera de especificar más clara y puntillosamente, tanto para el programador como para el compilador, qué tipos se pueden usar con un componente plantillado.

Introducción[editar]

Los conceptos son una extensión de la programación plantillada del lenguaje de programación C++ introducida en la versión C++20. Los conceptos son predicados booleanos con nombre, sobre parámetros de plantilla, evaluados en tiempo de compilación. Un concepto se puede asociar a una plantilla (plantilla de clase, plantilla de función, o función miembro de una clase plantillada), donde actúa como limitación: restringe los tipos aceptados por la plantilla.

Definición[editar]

Un concepto es una descripción de los requerimientos de un tipo o clase particular. En orientación a objetos, un concepto es similar a una interfaz, con la siguiente diferencia importante: mientras que la interfaz debe ser explicitada en la clase que la implementa, un concepto es más flexible y puede ser reconocido en una clase sin necesidad de que ésta lo explicite. Esto permite definir nuevos conceptos compatibles con clases preexistentes, sin necesidad de adaptarlas.

El término concepto está íntimamente asociado a C++. La idea general se descubre en otros lenguajes, con otros nombres: C# y Java emplean interfaces; Haskell emplea type classes.

Historia[editar]

El término se encontraba en uso en 1998 en el contexto de la biblioteca STL de C++ y se le atribuye a Alexander Stepanov, principal diseñador de esa biblioteca. El propio estándar de C++ de 1998 introdujo el término concepto como una descripción de requerimientos de una clase o tipo particular, pero sin impacto en el lenguaje.

C++11 introdujo una variante de la instrucción for, conocida como for range, que recorre los elementos de un rango. El rango en cuestión es un concepto. Fue la primera versión de C++ en utilizar conceptos (predefinidos y tácitos), aunque todavía no permitía al programador definir sus propios conceptos.

Una forma diferente de conceptos, popularmente conocidos como C++0x Concepts, fue provisoriamente aceptada en el working paper para C++11, pero se retiró en 2009.[1]

En la reunión del comité de estandarización de C++ en marzo de 2016, el grupo de trabajo de evolución propuso incorporar los conceptos en C++17 pero la propuesta fue rechazada de lleno,[2]​ y Concepts v1 se postergó volcándose al borrador de C++20.[3]

Los conceptos finalmente adoptados en C++20 son mucho más simples que aquellos rechazados en 2009, por lo que internamente se los suele llamar Concepts Lite.[4]

Ejemplos[editar]

EqualityComparable[editar]

C++20 introduce una biblioteca estándar basada en conceptos (Especificación Técnica ISO/IEC DTS 21425), separada de la biblioteca estándar clásica. Esta biblioteca incluye el concepto EqualityComparable, que se toma como ejemplo para demostrar la declaración de un concepto.

EqualityComparable es un concepto que exige que el tipo T admita las operaciones == y != con un resultado convertible a bool, y se declara de esta manera:

template<typename T>
concept EqualityComparable = requires(T a, T b) {
    { a == b } -> std::same_as<bool>;
    { a != b } -> std::same_as<bool>;
};

Una plantilla de función restringida por este concepto se puede declarar como sigue:

template <EqualityComparable T>
void f(const T&);

Alternativamente, de una manera más compacta:

void f(const EqualityComparable auto&);

Y se puede invocar de la manera habitual:

f(x);

Range[editar]

Según se define en el encabezado range de la biblioteca range, std::ranges::range es un concepto[5]​ que requiere un método begin y otro método end que operen sobre el mismo tipo T:

template< class T >
concept range = requires(T& t) {
  ranges::begin(t);
  ranges::end  (t);
};

Sintaxis[editar]

Un concepto es un requerimiento con nombre. El siguiente código ilustra la sintaxis definiendo un concepto simple:

template <class T, class U>
concept Derivada = std::is_base_of<U, T>::value;

Derivada es el nombre elegido por el programador para este concepto. El requerimiento es la parte que se encuentra a la derecha del signo igual, en este caso std::is_base_of<U, T>::value Los conceptos se pueden definir combinando otros conceptos con operaciones booleanas. No pueden definir recursivamente:

template <class T>
concept Entero = std::is_integral<T>::value;

template <class T>
concept EnteroConSigno = Entero<T> && std::is_signed<T>::value;

template <class T>
concept EnteroSinSigno = Entero<T> && !EnteroConSigno<T>;

Luego de definidos, los conceptos se aplican luego en plantillas. Se ilustran dos formas son correctas:

template<class T>
void f(T) requires EnteroSinSigno<T>;

template<class T> requires EnteroSinSigno<T>
void g(T);

Es posible usar conceptos anónimos, aplicando requerimientos directamente a las plantillas sin necesidad de asignarle un nombre.

template<class T>
void h(T) requires std::is_integral<T>::value;

Usos principales[editar]

Los usos principales de los conceptos en programación plantillada son:

  • Interfaz
  • Chequeo de tipos válidos
  • Mensajes de error más claros y simples
  • Selección automática de sobrecarga y especializaciones
  • Restricción de la deducción de tipo

Interfaz[editar]

En programación orientada a objetos las interfaces son declaraciones de métodos y propiedades en común en varias clases. Las clases que implementan la interfaz lo deben hacer explícitamente en su definición. En C++ las interfaces se implementan a través de clases abstractas y funciones virtuales.

Los conceptos permiten especificar requerimientos sobre una clase, con un objetivo similar al de una interfaz, pero con una ventaja notable: no hace falta implementar la interfaz en las clases, lo que simplifica su uso sobre bibliotecas estándares y de terceros.

Restricción a la deducción de tipo[editar]

auto es un declarador para deducción automática de tipo, introducido en C++11, que resultó ampliamente adoptado por los programadores.

Los conceptos se pueden combinar con auto para restringir la deducción automática, algo muy útil especialmente cuando de la misma expresión se deducen varios tipos en lugar de uno solo:

Sortable auto x = f(y); // sólo compila si el tipo de f(y) satisface el concepto Sortable

Mensajes de error del compilador[editar]

Si un programador intenta utilizar un tipo que no satisface los requisitos de la plantilla, el compilador generará un error. Cuándo no se usan conceptos estos errores son difíciles de entender porque el error no se informa en el contexto de la llamada, sino en el contexto de la implementación de la función plantillada, a menudo profundamente anidada, donde se usa el tipo.

Por ejemplo, std::sortrequiere que sus primeros dos argumentos sean iteradores de acceso aleatorio. Si un argumento no cumple con este requisito el error se producirá cuando sort intente utilizar sus parámetros como iteradores bidireccionales:

std::list<int> l = {2, 1, 3};
std::sort(l.begin(), l.end());

Un diagnóstico típico del compilador sin conceptos supera las 50 líneas de mensaje:

In instantiation of 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = std::_List_iterator<int>; _Compare = __gnu_cxx::__ops::_Iter_less_iter]':
error: no match for 'operator-' (operand types are 'std::_List_iterator<int>' and 'std::_List_iterator<int>')
std::__lg(__last - __first) * 2, 
...

Usando conceptos el error se detecta e informa en el contexto de la llamada, y resulta mucho más claro:

error: cannot call function 'void std::sort(_RAIter, _RAIter) [with _RAIter = std::_List_iterator<int>]'
note: concept 'RandomAccessIterator()' was not satisfied

Véase también[editar]

Referencias[editar]

  1. Bjarne Stroustrup (22 de julio de 2009). «The C++0x "Remove Concepts" Decision». Dr. Dobbs. 
  2. Honermann, Tom (6 de marzo de 2016). «Why Concepts didn’t make C++17». honermann.net. Archivado desde el original el 2 de octubre de 2018. Consultado el 30 de diciembre de 2020. 
  3. «2017 Toronto ISO C++ Committee Discussion Thread (Concepts in C++20; Coroutines, Ranges and Networking TSes published) : cpp». 
  4. Andrew Sutton (24 de febrero de 2013). «Concepts Lite: Constraining Templates with Predicates». isocpp.org. 
  5. «std::ranges::range». 

Enlaces externos[editar]