Cómo determinar si usar o ?

7 minutos de lectura

avatar de usuario
BrainStone

¿Hay alguna manera de determinar si puedo usar el estándar? <filesystem> (que está disponible en todos los compiladores C++ modernos que admiten C++17) o <experimental/filesystem> que es utilizado por los compiladores más antiguos. (Por ejemplo, g ++ 6.3, que es la versión estándar actual en Debian Stretch)

Saber cuál usar es importante porque el primero usa std::filesystem::xxx y el último std::experimental::filesystem::xxx.

  • Siempre usé un alias de espacio de nombres, por lo que cambiar fue trivial namespace fs = std::filesystem; de namespace fs = std::experimental::filesystem; Luego declara todo en términos del alias: fs::path p = ...;

    – Galik

    18/11/2018 a las 21:25

  • Saber cuál usar es importante porque…“Es más importante que eso, ya que hay diferencias de comportamiento entre ellos.

    – Nicolás Bolas

    18 de noviembre de 2018 a las 21:28

  • Envidio el apoyo de su biblioteca.

    – johnathan

    18/11/2018 a las 21:31

  • @NicolBolas es por eso que creé el fragmento en mi respuesta a continuación, ya que permite ajustes con el preprocesador en el código según la versión que se esté utilizando.

    – BrainStone

    18 de noviembre de 2018 a las 21:37

avatar de usuario
BrainStone

Normalmente creo un encabezado filesystem.hpp con el siguiente contenido:

// We haven't checked which filesystem to include yet
#ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL

// Check for feature test macro for <filesystem>
#   if defined(__cpp_lib_filesystem)
#       define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0

// Check for feature test macro for <experimental/filesystem>
#   elif defined(__cpp_lib_experimental_filesystem)
#       define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1

// We can't check if headers exist...
// Let's assume experimental to be safe
#   elif !defined(__has_include)
#       define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1

// Check if the header "<filesystem>" exists
#   elif __has_include(<filesystem>)

// If we're compiling on Visual Studio and are not compiling with C++17, we need to use experimental
#       ifdef _MSC_VER

// Check and include header that defines "_HAS_CXX17"
#           if __has_include(<yvals_core.h>)
#               include <yvals_core.h>

// Check for enabled C++17 support
#               if defined(_HAS_CXX17) && _HAS_CXX17
// We're using C++17, so let's use the normal version
#                   define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
#               endif
#           endif

// If the marco isn't defined yet, that means any of the other VS specific checks failed, so we need to use experimental
#           ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
#               define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
#           endif

// Not on Visual Studio. Let's use the normal version
#       else // #ifdef _MSC_VER
#           define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
#       endif

// Check if the header "<filesystem>" exists
#   elif __has_include(<experimental/filesystem>)
#       define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1

// Fail if neither header is available with a nice error message
#   else
#       error Could not find system header "<filesystem>" or "<experimental/filesystem>"
#   endif

// We priously determined that we need the exprimental version
#   if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
// Include it
#       include <experimental/filesystem>

// We need the alias from std::experimental::filesystem to std::filesystem
namespace std {
    namespace filesystem = experimental::filesystem;
}

// We have a decent compiler and can use the normal version
#   else
// Include it
#       include <filesystem>
#   endif

#endif // #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL

Incluso crea un alias para std::experimental::filesystem a std::filesystem si se utilizan los encabezados experimentales.
Lo que significa que simplemente puede incluir este encabezado en lugar de <filesystem>usar std::filesystem::xxx y disfruta también del soporte de compiladores más antiguos.

Algunas notas sobre los detalles de este fragmento:

  • __cpp_lib_filesystem y __cpp_lib_experimental_filesystem

    Estos son Macros de prueba de características. Deben estar disponibles cuando los encabezados respectivos estén disponibles. Pero VisualStudio 2015 (y versiones posteriores) no los admiten. Entonces, el resto es solo para asegurarnos de que podamos hacer una evaluación precisa, en lugar de depender de macros poco confiables.

  • __has_include()

    Si bien la mayoría de los compiladores tienen esa macro incorporada, no hay garantía, ya que no está en el estándar. Mi fragmento verifica su existencia antes de usarlo. Y en caso de que no exista, suponemos que tenemos que usar la versión experimental para proporcionar la máxima compatibilidad.

  • defined(_MSC_VER) && !(defined(_HAS_CXX17) && _HAS_CXX17)

    Algunas versiones de VisualStudio (a saber, 2015) tienen solo una implementación a medias de C ++ 17. Y es posible que el <filesystem> el encabezado existe, pero std::filesystem no. Esta línea verifica ese caso y usa la versión experimental en su lugar.

  • #error ...

    Si la verificación de encabezado está disponible y no podemos encontrar ninguno de los encabezados, simplemente imprimimos un buen error, ya que no hay nada que podamos hacer.

  • INCLUDE_STD_FILESYSTEM_EXPERIMENTAL

    Incluso obtiene un marco que le permite saber qué versión está en uso para que pueda escribir sus propias declaraciones previas al procesador que traten las diferencias entre las versiones.

  • namespace filesystem = experimental::filesystem;

    Esta definición de alias es solo para convencer de que se asegurará de que tendrá std::filesystemsuponiendo que su compilador le permita hacerlo (no he visto uno solo que no permita eso).
    De acuerdo con el estándar que define cualquier cosa en el std el espacio de nombres es un comportamiento indefinido. Entonces, si su compilador, conciencia, colegas, estándar de código o lo que sea se queja, simplemente defina namespace fs = std::experimental::filesystem; en el bloque superior y namespace fs = std::filesystem; en el inferior (Solo para estar seguro, si hace eso, quite el namespace std { cosas)

PD: creé la respuesta y esta pregunta, porque pasé mucho tiempo frustrado porque los compiladores más antiguos no tenían el <filesystem> encabezamiento. Después de una buena cantidad de investigación y pruebas en múltiples plataformas con múltiples compiladores y versiones de ellos, logré encontrar esta solución universal. Lo he probado con VisualStudio, g ++ y clang (solo con versiones que realmente tienen al menos soporte experimental para C ++ 17).
Si hay un problema con otro compilador, házmelo saber y haré que funcione para él también.

  • No voté en contra, pero puede ser porque, técnicamente, poner algo no autorizado en namespace std es comportamiento indefinido. Usualmente uso un espacio de nombres diferente en lugar de std.

    – Galik

    18 de noviembre de 2018 a las 21:38


  • No es lo mejor para hacerlo, pero servirá para una solución simple lista para usar. Como personalmente prefiero usar los nombres completos a través del código. Naturalmente, se puede cambiar a cualquier cosa. Aunque es un punto justo y lo señalaré en mis explicaciones.

    – BrainStone

    18/11/2018 a las 21:40

  • Es poco probable que cause problemas, supongo. Incluso lo hice yo mismo para algunos programas internos, pero luego cambié al alias del sistema de archivos para fs::.

    – Galik

    18 de noviembre de 2018 a las 21:42

  • Realmente no debería causar problemas en ningún compilador decente. Pero solo para estar seguro, agregué una nota que explica una alternativa en caso de que se convierta en un problema.

    – BrainStone

    18/11/2018 a las 21:45

avatar de usuario
chris mc

Normalmente uso el macros de prueba de características mucho para este tipo de problema. Actualmente me enfrento con este problema exacto, pero he usado __cpp_lib_filesystem junto con using palabra clave.

// since C++ 20
#include <version>

#ifdef __cpp_lib_filesystem
    #include <filesystem>
    using fs = std::filesystem;
#elif __cpp_lib_experimental_filesystem
    #include <experimental/filesystem>
    using fs = std::experimental::filesystem;
#else
    #error "no filesystem support="("
#endif

Estoy usando esto en gcc-6 y versiones posteriores, así como en clang-6, lamentablemente no hay una copia anterior de Studio para probar, pero funciona en 15.7 y versiones posteriores.

  • ¿Están definidos en la norma?

    – BrainStone

    19 de noviembre de 2018 a las 0:05

  • ¡sí! si sigue el enlace, todos se adjuntan a las propuestas. Siempre que el comité lo aprueba asigna esta información. Esto es cierto para cualquier característica de C++ 11 o posterior

    – Chris Mc

    19 de noviembre de 2018 a las 0:11


  • Un problema que encontré es que estas macros solo se definen después de cargar cualquier encabezado estándar en Visual Studio 2017. Y ni siquiera existe para Visual Studio 2015 y versiones posteriores. Los incluiré en mi solución, para que los compiladores con compatibilidad adecuada con C++11+ puedan determinar más fácilmente la versión correcta.

    – BrainStone

    19 de noviembre de 2018 a las 17:48

  • ¿No tiene que incluir para el _cpp_lib* macros para estar allí?

    – Llama de fuego

    5 oct 2020 a las 15:36

  • ¿Cuál es el propósito de usar un encabezado provisto por C++ 20 para verificar si algo es experimental o no en C++ 17? Podría estar extendiéndolo, pero ¿cuáles son las posibilidades de que un compilador pueda ejecutar código que requiere C ++ 20, no compatible filesystem como una característica no experimental?

    – rbaleksandar

    14 de septiembre de 2021 a las 8:49

¿Ha sido útil esta solución?