Lograr compatibilidad con C ++ 11

12

Trabajo en una gran aplicación de software que debe ejecutarse en varias plataformas. Algunas de estas plataformas admiten algunas características de C ++ 11 (por ejemplo, MSVS 2010) y otras no admiten ninguna (por ejemplo, GCC 4.3.x). Espero que esta situación continúe por varios años (mi mejor estimación: 3-5 años).

Dado eso, me gustaría configurar una interfaz de compatibilidad para que (en la medida de lo posible) las personas puedan escribir código C ++ 11 que aún se compilará con compiladores más antiguos con un mínimo de mantenimiento. En general, el objetivo es minimizar # ifdef's tanto como sea razonablemente posible mientras se habilita la sintaxis / características básicas de C ++ 11 en las plataformas que las admiten y proporcionar emulación en las plataformas que no lo hacen.

Comencemos con std :: move (). La forma más obvia de lograr compatibilidad sería colocar algo como esto en un archivo de encabezado común:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

Esto permite a las personas escribir cosas como

std::vector<Thing> x = std::move(y);

... con impunidad. Hace lo que quieren en C ++ 11 y lo hace lo mejor que puede en C ++ 03. Cuando finalmente eliminamos el último compilador de C ++ 03, este código puede permanecer tal cual.

Sin embargo, según el estándar, es ilegal inyectar nuevos símbolos en el stdespacio de nombres. Esa es la teoria. Mi pregunta es: en términos prácticos, ¿hay algún daño en hacer esto como una forma de lograr la compatibilidad hacia adelante?

mcmcc
fuente
1
Boost ya proporciona bastante de esto, y ya tiene código para usar nuevas funciones cuando / donde esté disponible, por lo que es posible que pueda usar lo que proporciona Boost y terminar con eso. Por supuesto, hay limitaciones: la mayoría de las nuevas características se agregaron específicamente porque las soluciones basadas en bibliotecas no son adecuadas.
Jerry Coffin
Sí, estoy pensando específicamente en esas características que se pueden implementar a nivel de biblioteca, no en los cambios sintácticos. Boost realmente no aborda el problema de la compatibilidad directa (sin interrupciones). A menos que me falte algo ...
mcmcc
Gcc 4.3 ya tiene un buen puñado de características de C ++ 11, siendo las referencias Rvalue las más importantes.
Jan Hudec
@ JanHudec: Tienes razón. Pobre ejemplo. En cualquier caso, hay otros compiladores que definitivamente no admiten la sintaxis (por ejemplo, cualquier versión del compilador C ++ de IBM que tengamos).
mcmcc

Respuestas:

9

He estado trabajando durante un buen tiempo para mantener un nivel de compatibilidad hacia adelante y hacia atrás en mis programas C ++, hasta que finalmente tuve que hacer un kit de herramientas de la biblioteca , que estoy preparando para el lanzamiento ya se ha lanzado. En general, siempre que acepte que no obtendrá una compatibilidad hacia adelante "perfecta" ni en las características (algunas cosas simplemente no se pueden emular hacia adelante) no en la sintaxis (probablemente tendrá que usar macros, espacios de nombres alternativos para algunas cosas) entonces ya está todo listo.

Hay una gran cantidad de características que se pueden emular en C ++ 03 en un nivel que es suficiente para un uso práctico, y sin todas las molestias que conlleva, por ejemplo: Boost. Diablos, incluso la propuesta de estándares de C ++ nullptrsugiere un backport C ++ 03. Y luego está TR1, por ejemplo, para todo lo relacionado con C ++ 11, pero hemos tenido previsualizaciones durante años. ¡No solo eso, algunas características de C ++ 14 como variantes de aserción, functores transparentes y optional se pueden implementar en C ++ 03!

Las dos únicas cosas que sé que no se pueden respaldar son las plantillas constexpr y variadic.

Con respecto a todo el asunto de agregar cosas al espacio de nombres std, mi opinión es que no importa , en absoluto. Piense en Boost, una de las bibliotecas de C ++ más importantes y relevantes, y su implementación de TR1: Boost.Tr1. Si desea mejorar C ++, hágalo compatible con C ++ 11, luego, por definición, lo está convirtiendo en algo que no es C ++ 03, por lo que bloquearse a sí mismo en un Estándar que tiene la intención de evitar o dejar atrás de todos modos es En pocas palabras, contraproducente. Los puristas se quejarán, pero por definición uno no debe preocuparse por ellos.

Por supuesto, solo porque no seguirás el Estándar (03) después de todo no significa que no puedas intentarlo, o que irás alegremente rompiéndolo. Ese no es el punto. Siempre y cuando mantenga un control muy cuidadoso de lo que se agrega al stdespacio de nombres y controle los entornos en los que se utiliza su software (es decir, ¡haga las pruebas!), No debería haber ningún daño irremediable. Si es posible, defina todo en un espacio de nombres separado y solo agregue usingdirectivas al espacio de nombres stdpara que no agregue nada más allá de lo que "absolutamente" debe incluir. Lo cual, IINM, es más o menos lo que hace Boost.TR1.


Actualización (2013) : como solicitud de la pregunta original y viendo algunos de los comentarios que no puedo agregar debido a la falta de representación, aquí hay una lista de características de C ++ 11 y C ++ 14 y su grado de portabilidad a C ++ 03:

  • nullptr: completamente implementable dado el respaldo oficial del Comité; probablemente también tendrá que proporcionar algunas especializaciones de type_traits para que se reconozca como un tipo "nativo".
  • forward_list: completamente implementable, aunque el soporte del asignador se basa en lo que puede proporcionar su implicación Tr1.
  • Nuevos algoritmos (partición_copias, etc.): totalmente implementables.
  • Construcciones de contenedores a partir de secuencias de llaves (p. Ej . :) vector<int> v = {1, 2, 3, 4};: completamente implementable, aunque más extenso de lo que uno quisiera.
  • static_assert: casi completamente implementable cuando se implementa como una macro (solo tendrá que tener cuidado con las comas).
  • unique_ptr: casi completamente implementable, pero también necesitará soporte del código de llamada (para almacenarlos en contenedores, etc.); Ver el siguiente sin embargo.
  • rvalue-references: casi completamente implementable dependiendo de cuánto espere obtener de ellos (por ejemplo: Boost Move).
  • Para cada iteración: casi completamente implementable, la sintaxis será algo diferente.
  • usando funciones locales como argumentos (por ejemplo: transformación): casi completamente implementable, pero la sintaxis diferirá lo suficiente; por ejemplo, las funciones locales no están definidas en el sitio de la llamada, sino justo antes.
  • operadores de conversión explícitos: implementables a niveles prácticos (haciendo que la conversión se haga explícita), ver "cast_cast "de Imperfect C ++ ; pero la integración con características del lenguaje como static_cast<>podría ser casi imposible.
  • reenvío de argumentos: implementable a niveles prácticos dado lo anterior en rvalue-references, pero necesitará proporcionar N sobrecargas a sus funciones tomando argumentos reenviables.
  • mover: implementable a niveles prácticos (ver los dos anteriores). Por supuesto, tendría que usar contenedores modificadores y objetos para beneficiarse de esto.
  • Asignadores de ámbito: no es realmente implementable a menos que su implementación Tr1 pueda ayudarlo.
  • Tipos de caracteres multibyte: No es realmente implementable a menos que su Tr1 pueda soportarlo. Pero para el propósito previsto, es mejor confiar en una biblioteca específicamente diseñada para tratar el asunto, como la UCI, incluso si usa C ++ 11.
  • Listas de argumentos variables: implementable con algunos problemas, preste atención al reenvío de argumentos.
  • noexcept: depende de las características de su compilador.
  • Nuevos autosemántica y decltype: depende de las características del compilador - por ejemplo .: __typeof__.
  • tipos enteros de tamaño ( int16_t, etc.): depende de las características de su compilador, o puede delegar en el stdint.h portátil.
  • atributos de tipo: depende de las características de su compilador.
  • Lista de inicializadores: no se puede implementar, que yo sepa; sin embargo, si lo que desea es inicializar contenedores con secuencias, consulte lo anterior sobre "construcciones de contenedores".
  • Alias ​​de plantillas: no es implementable, que yo sepa, pero es una característica innecesaria de todos modos, y hemos tenido ::typeen plantillas para siempre
  • Plantillas variadas: no se puede implementar a mi entender; el cierre es el argumento de plantilla predeterminado, que requiere N especializaciones, etc.
  • constexpr: No se puede implementar a mi entender.
  • Inicialización uniforme: no es implementable, que yo sepa, pero la inicialización de constructor predeterminada garantizada se puede implementar con el valor inicializado de Boost.
  • C ++ 14 dynarray: totalmente implementable.
  • C ++ 14 optional<>: casi completamente implementable siempre que su compilador C ++ 03 admita configuraciones de alineación.
  • Funcionarios transparentes C ++ 14: casi completamente implementables, pero su código de cliente probablemente tendrá que usar explícitamente, por ejemplo: std::less<void>para que funcione.
  • C ++ 14 nuevas variantes de aserción (como assure): completamente implementable si desea afirmaciones, casi completamente implementable si desea habilitar tiros en su lugar.
  • Extensiones de tupla de C ++ 14 (obtenga el elemento de tupla por tipo): totalmente implementable, e incluso puede hacer que no se compile con los casos exactos descritos en la propuesta de características.

(Descargo de responsabilidad: varias de estas características se implementan en mi biblioteca de backports C ++ que he vinculado anteriormente, por lo que creo que sé de lo que estoy hablando cuando digo "completamente" o "casi completamente").

Luis Machuca
fuente
6

Esto es fundamentalmente imposible. Considere std::unique_ptr<Thing>. Si fuera posible emular referencias rvalue como una biblioteca, no sería una característica del lenguaje.

DeadMG
fuente
1
Dije "en la medida de lo posible". Claramente, algunas características deberán dejarse atrás # ifdef o no se utilizarán en absoluto. std :: move () resulta ser uno que puede admitir la sintaxis (aunque no la funcionalidad).
mcmcc
2
En realidad, la propuesta de referencias rvalue menciona una solución basada en la biblioteca.
Jan Hudec
Más específicamente, es posible implementar la semántica de movimiento para una clase particular en C ++ 03, por lo que debería ser posible definirla std::unique_ptrallí, pero hay algunas otras características de las referencias rvalue que no se pueden implementar en C ++ 03, por std::forwardlo que no es posible. La otra cosa es que std::unique_ptrno será útil, porque las colecciones no usarán la semántica de movimiento a menos que las reemplace todas.
Jan Hudec
@ JanHudec: No es posible definirlo unique_ptr. Mira las fallas de auto_ptr. unique_ptres prácticamente el ejemplo de libro de texto de una clase cuya semántica fue fundamentalmente habilitada por la función del lenguaje.
DeadMG
@DeadMG: No, no es unique_ptrque haya sido habilitado fundamentalmente por la función de idioma. Sin embargo, no sería muy útil sin esa característica. porque sin reenvío perfecto no sería utilizable en muchos casos y el reenvío perfecto requiere esa característica.
Jan Hudec
2
  1. Gcc comenzó a introducir C ++ 11 (todavía C ++ 0x en ese momento) en 4.3. Esta tabla dice que ya tiene referencias de valor y algunas otras características menos utilizadas (debe especificar la -std=c++0xopción para habilitarlas).
  2. Muchas adiciones a la biblioteca estándar en C ++ 11 ya estaban definidas en TR1 y GNU stdlibc ++ las proporciona en el espacio de nombres std :: tr1. Así que solo haga un uso condicional apropiado.
  3. Boost define la mayoría de las funciones TR1 y puede inyectarlas en el espacio de nombres TR1 si no lo tiene (pero VS2010 sí y gcc 4.3 también si usa GNU stdlibc ++).
  4. Poner cualquier cosa en el stdespacio de nombres es un "comportamiento indefinido". Eso significa que la especificación no dice lo que sucederá. Pero si sabe que en una plataforma en particular la biblioteca estándar no define algo, simplemente continúe y defínalo. Solo espere que tenga que verificar en cada plataforma lo que necesita y lo que puede definir.
  5. La propuesta de referencias de valor, N1690 menciona cómo implementar la semántica de movimiento en C ++ 03. Eso podría usarse para sustituir unique_ptr. Sin embargo, no sería demasiado útil, ya que depende de colecciones que realmente usan la semántica de movimiento y las de C ++ 03 obviamente no lo harán.
Jan Hudec
fuente
1
Tienes razón sobre GCC, pero desafortunadamente, también tengo que admitir otros compiladores (no GCC). Su viñeta n. ° 4 está en el centro de la pregunta que hago. El n. ° 5 es interesante, pero no estoy buscando admitir la semántica de movimiento (la optimización de copia) en estas plataformas más antiguas, sino simplemente "std :: move ()" como una sintaxis compilable.
mcmcc