una todas salida que programacion procesador preprocesamiento macro lenguaje las funciones entrada encuentran directivas directiva definicion defines define declarar cuál cual c++ c templates metaprogramming

todas - directivas de preprocesamiento c++



__FILE__ manipulación de la macro manipulación en tiempo de compilación (8)

Actualmente no hay forma de hacer un procesamiento completo de cadenas en tiempo de compilación (el máximo que podemos usar en las plantillas son los extraños literales de cuatro caracteres).

¿Por qué no simplemente guardar el nombre procesado estáticamente, por ejemplo:

namespace { const std::string& thisFile() { static const std::string s(prepocessFileName(__FILE__)); return s; } }

De esta manera, solo estás haciendo el trabajo una vez por archivo. Por supuesto, también puede envolver esto en una macro, etc.

Uno de los problemas que tuve al trasladar algunas cosas de Solaris a Linux es que el compilador de Solaris expande la macro __FILE__ durante el preproceso al nombre del archivo (ej. MyFile.cpp) mientras que gcc en Linux se expande a la ruta completa (ej. / Home /user/MyFile.cpp). Esto se puede resolver razonablemente fácilmente usando basename () pero .... si lo está usando mucho, entonces todas esas llamadas a basename () tienen que sumarse, ¿no?

Aquí está la pregunta. ¿Hay alguna manera de usar plantillas y metaprogramación estática para ejecutar basename () o similar en tiempo de compilación? Como __FILE__ es constante y conocido en el momento de la compilación, esto podría facilitarlo. ¿Qué piensas? Se puede hacer?


es posible que desee probar la macro __BASE_FILE__ . Esta página describe una gran cantidad de macros compatibles con gcc.


Usando C ++ 11, tienes un par de opciones. Primero definamos:

constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1) { return path [index] ? ( path [index] == ''/'' ? basename_index (path, index + 1, index) : basename_index (path, index + 1, slash_index) ) : (slash_index + 1) ; }

Si su compilador admite expresiones de declaración y desea asegurarse de que el cálculo del nombre de base se realiza en tiempo de compilación, puede hacer esto:

// stmt-expr version #define STRINGIZE_DETAIL(x) #x #define STRINGIZE(x) STRINGIZE_DETAIL(x) #define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__);/ static_assert (basename_idx >= 0, "compile-time basename"); / __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;})

Si su compilador no admite expresiones de declaración, puede usar esta versión:

// non stmt-expr version #define __FILELINE__ (__FILE__ ":" STRINGIZE(__LINE__) ": " + basename_index(__FILE__))

Con esta versión no stmt-expr, gcc 4.7 y 4.8 invocan basename_index en tiempo de ejecución, por lo que es mejor utilizar la versión stmt-expr con gcc. ICC 14 produce código óptimo para ambas versiones. ICC13 no puede compilar la versión stmt-expr y produce un código subóptimo para la versión no stmt-expr.

Solo para completar, aquí está el código, todo en un solo lugar:

#include <iostream> #include <stdint.h> constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1) { return path [index] ? ( path [index] == ''/'' ? basename_index (path, index + 1, index) : basename_index (path, index + 1, slash_index) ) : (slash_index + 1) ; } #define STRINGIZE_DETAIL(x) #x #define STRINGIZE(x) STRINGIZE_DETAIL(x) #define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__); / static_assert (basename_idx >= 0, "compile-time basename"); / __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;}) int main() { std::cout << __FILELINE__ << "It works" << std::endl; }


En proyectos que usan CMake para controlar el proceso de compilación, puede usar una macro como esta para implementar una versión portátil que funcione en cualquier compilador o plataforma. Aunque personalmente, te tengo lástima si debes usar algo que no sea gcc ... :)

# Helper function to add preprocesor definition of FILE_BASENAME # to pass the filename without directory path for debugging use. # # Example: # # define_file_basename_for_sources(my_target) # # Will add -DFILE_BASENAME="filename" for each source file depended on # by my_target, where filename is the name of the file. # function(define_file_basename_for_sources targetname) get_target_property(source_files "${targetname}" SOURCES) foreach(sourcefile ${source_files}) # Get source file''s current list of compile definitions. get_property(defs SOURCE "${sourcefile}" PROPERTY COMPILE_DEFINITIONS) # Add the FILE_BASENAME=filename compile definition to the list. get_filename_component(basename "${sourcefile}" NAME) list(APPEND defs "FILE_BASENAME=/"${basename}/"") # Set the updated compile definitions on the source file. set_property( SOURCE "${sourcefile}" PROPERTY COMPILE_DEFINITIONS ${defs}) endforeach() endfunction()

Luego, para usar la macro, solo llámala con el nombre del objetivo CMake:

define_file_basename_for_sources(myapplication)


Para Objective-C, la siguiente macro proporciona un CString que puede reemplazar la macro __FILE__ , pero omitiendo los componentes de ruta iniciales.

#define __BASENAME__ [[[NSString stringWithCString:__FILE__ / encoding:NSUTF8StringEncoding] / lastPathComponent] / cStringUsingEncoding:NSUTF8StringEncoding]

Es decir, convierte: /path/to/source/sourcefile.m en: sourcefile.m

Funciona tomando la salida de la macro __FILE__ (que es una cadena terminada en nulo con formato C), convirtiéndola en un objeto de cadena Objective-C, luego eliminando los componentes de ruta iniciales y finalmente convirtiéndolo de nuevo en una cadena con formato C .

Esto es útil para obtener un formato de registro que sea más legible, reemplazando (por ejemplo) una macro de registro como esta:

#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), / __FILE__, __LINE__, ##__VA_ARGS__)

con:

#define __BASENAME__ [[[NSString stringWithCString:__FILE__ / encoding:NSUTF8StringEncoding] / lastPathComponent] / cStringUsingEncoding:NSUTF8StringEncoding] #define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), / __BASENAME__, __LINE__, ##__VA_ARGS__)

Contiene algunos elementos de tiempo de ejecución, y en ese sentido no cumple completamente con la pregunta, pero probablemente sea apropiado para la mayoría de las circunstancias.


Otro método constexpr C ++ 11 es el siguiente:

constexpr const char * const strend(const char * const str) { return *str ? strend(str + 1) : str; } constexpr const char * const fromlastslash(const char * const start, const char * const end) { return (end >= start && *end != ''/'' && *end != ''//') ? fromlastslash(start, end - 1) : (end + 1); } constexpr const char * const pathlast(const char * const path) { return fromlastslash(path, strend(path)); }

El uso es bastante simple también:

std::cout << pathlast(__FILE__) << "/n";

El constexpr se realizará en tiempo de compilación si es posible, de lo contrario será una constexpr de la ejecución de las sentencias en tiempo de ejecución.

El algoritmo es un poco diferente en que encuentra el final de la cadena y luego trabaja hacia atrás para encontrar la última barra inclinada. Probablemente sea más lento que la otra respuesta, pero dado que está destinado a ejecutarse en tiempo de compilación, no debería ser un problema.


Otro enfoque posible cuando se usa CMake es agregar una definición de preprocesador personalizada que usa directamente las variables automáticas de make (a costa de un escape posiblemente feo):

add_definitions(-D__FILENAME__=//"$/(<F/)//")

O bien, si está utilizando CMake> = 2.6.0:

cmake_policy(PUSH) cmake_policy(SET CMP0005 OLD) # Temporarily disable new-style escaping. add_definitions(-D__FILENAME__=//"$/(<F/)//") cmake_policy(POP)

(De lo contrario CMake escapará de cosas ).

Aquí, aprovechamos el hecho de make sustituciones $(<F) con el nombre del archivo de origen sin componentes principales y esto debería aparecer como -D__FILENAME__=/"MyFile.cpp/" en el comando del compilador ejecutado.

(Mientras make la documentación de make recomienda usar $(notdir path $<) lugar, no tener espacios en blanco en la definición agregada parece gustar mejor a CMake).

Luego puede usar __FILENAME__ en su código fuente como usaría __FILE__ . Para fines de compatibilidad, es posible que desee agregar una alternativa segura:

#ifndef __FILENAME__ #define __FILENAME__ __FILE__ #endif


Me gusta la respuesta de @Chetan Reddy , que sugiere usar static_assert() en una expresión de declaración para forzar una llamada en tiempo de compilación para encontrar la última barra, y así evitar la sobrecarga del tiempo de ejecución.

Sin embargo, las expresiones de instrucciones son una extensión no estándar y no son universalmente compatibles. Por ejemplo, no pude compilar el código de esa respuesta en Visual Studio 2017 (MSVC ++ 14.1, creo).

En su lugar, ¿por qué no utilizar una plantilla con un parámetro entero, como por ejemplo:

template <int Value> struct require_at_compile_time { static constexpr const int value = Value; };

Habiendo definido una plantilla así, podemos usarla con la función basename_index() de la respuesta de @Chetan Reddy:

require_at_compile_time<basename_index(__FILE__)>::value

Esto asegura que basename_index(__FILE__) de hecho será llamado en tiempo de compilación, ya que es cuando el argumento de la plantilla debe ser conocido.

Con esto, el código completo para, vamos a llamarlo JUST_FILENAME , macro, evaluando solo el componente de nombre de archivo de __FILE__ se vería así:

constexpr int32_t basename_index ( const char * const path, const int32_t index = 0, const int32_t slash_index = -1 ) { return path [index] ? ((path[index] == ''/'' || path[index] == ''//') // (see below) ? basename_index (path, index + 1, index) : basename_index (path, index + 1, slash_index) ) : (slash_index + 1) ; } template <int32_t Value> struct require_at_compile_time { static constexpr const int32_t value = Value; }; #define JUST_FILENAME (__FILE__ + require_at_compile_time<basename_index(__FILE__)>::value)

He robado basename_index() casi textualmente de la respuesta mencionada anteriormente , excepto que agregué un cheque para el separador de barra invertida específica de Windows.