__FILE__ обработка макросов во время компиляции

16

Одна из проблем, которые я испытывал при переносе некоторых вещей из Solaris в Linux, заключается в том, что компилятор Solaris расширяет макрос __FILE__ во время предварительной обработки имени файла (например, MyFile.cpp), тогда как gcc в Linux расширяется до полного путь (например,/home/user/MyFile.cpp). Это можно легко разрешить с помощью basename(), но.... если вы используете его много, то все те обращения к basename() должны складываться, не так ли?

Вот вопрос. Есть ли способ использования шаблонов и статического метапрограммирования, для запуска basename() или подобного во время компиляции? Поскольку __FILE__ является постоянным и известен во время компиляции, это может сделать его проще. Как вы думаете? Это можно сделать?

  • 1
    Несколько быстрых экспериментов показывают, что __FILE__ расширяется до имени файла, указанного в командной строке, которое может быть либо абсолютным, либо относительным. Разница скорее всего в Makefile. __BASE_FILE__ , расширение gcc, отличается только тем, что оно дает самое внешнее имя файла, а не что-либо #include d.
Теги:
templates
metaprogramming

8 ответов

6
Лучший ответ

В настоящее время нет способа выполнить полную обработку строк во время компиляции (максимум, с которым мы можем работать в шаблонах, - это странные четырехзначные литеры).

Почему бы просто не просто сохранить обработанное имя статически, например:

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

Таким образом, вы выполняете только один раз для каждого файла. Конечно, вы можете также обернуть это в макрос и т.д.

18

Используя С++ 11, у вас есть несколько вариантов. Пусть сначала определите:

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)
     ;
}

Если ваш компилятор поддерживает выражения операторов, и вы хотите быть уверенным, что вычисление базового имени выполняется во время компиляции, вы можете сделать это:

// 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;})

Если ваш компилятор не поддерживает выражения операторов, вы можете использовать эту версию:

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

С этой версией non stmt-expr gcc 4.7 и 4.8 вызывают basename_index во время выполнения, поэтому вам лучше использовать версию stmt-expr с gcc. ICC 14 создает оптимальный код для обеих версий. ICC13 не может скомпилировать версию stmt-expr и создает субоптимальный код для версии non stmt-expr.

Просто для полноты, здесь код все в одном месте:

#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;                                                                                                       
}                                                                                                                                                             
  • 0
    Идея использования static_assert () в выражении оператора очень полезна и прекрасно работает с GCC, однако, поскольку выражения оператора являются нестандартным расширением, как вы сказали, это решение не универсально. Я думаю, что есть способ принудительно оценить время компиляции, не полагаясь на выражения операторов, используя шаблон с целочисленным параметром. Я опубликовал ответ, описывающий его, который использует basename_index() из этого ответа.
8

В проектах, использующих CMake для управления процессом сборки, вы можете использовать такой макрос, чтобы реализовать переносимую версию, которая работает на любом компиляторе или платформе. Хотя лично мне жаль вас, если вы должны использовать что-то другое, кроме 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 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()

Затем, чтобы использовать макрос, просто вызовите его с именем цели CMake:

define_file_basename_for_sources(myapplication)
  • 0
    Операция не упоминает cmake, но для людей, которые ищут решение cmake, это такой изумительный ответ.
6

вы можете попробовать макрос __BASE_FILE__. Эта страница описывает много макросов, поддерживаемых gcc.

  • 2
    Отличный момент. Если это приводит к ошибкам компиляции в Solaris, и вам необходимо поддерживать оба, добавьте ifdefs для проверки на BASE_FILE и используйте FILE, если BASE_FILE отсутствует.
  • 1
    На самом деле это не очень хороший момент. BASE_FILE в Solaris по-прежнему содержит путь к файлу, а не только его компонент имени.
Показать ещё 1 комментарий
4

Другой метод С++ 11 constexpr выглядит следующим образом:

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));
}

Использование довольно просто:

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

constexpr будет выполняться во время компиляции, если это возможно, в противном случае оно будет отвисеть от выполнения команд выполнения.

Алгоритм немного отличается тем, что находит конец строки и затем работает назад, чтобы найти последнюю косую черту. Это, вероятно, медленнее, чем другой ответ, но поскольку он предназначен для выполнения во время компиляции, это не должно быть проблемой.

  • 1
    Хорошая идея, но она не работает должным образом. || следует заменить на && для исправления.
2

Другим возможным подходом при использовании CMake является добавление пользовательского определения препроцессора, которое напрямую использует make автоматические переменные (за счет некоторых возможно уродливое побег):

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

Или, если вы используете 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)

(В противном случае CMake будет избегать вещей.)

Здесь мы используем тот факт, что make заменяет $(<F) на имя исходного файла без ведущих компонентов, и это должно отображаться как -D__FILENAME__=\"MyFile.cpp\" в исполняемой команде компилятора.

(Хотя make документация рекомендует использовать $(notdir path $<) вместо этого, не имея пробелов в добавленном определении, похоже, лучше CMake.)

Затем вы можете использовать __FILENAME__ в своем исходном коде, как если бы вы использовали __FILE__. В целях совместимости вы можете добавить безопасный резерв:

#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
1

Для Objective-C следующий макрос предоставляет CString, который может заменить макрос __FILE__, но опуская исходные компоненты пути.

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

То есть он преобразует: /path/to/source/sourcefile.m в: sourcefile.m

Он работает, беря на выходе макрос __FILE__ (который является строкой с нулевым символом C), преобразовывая его в объект строки Objective-C, затем удаляя исходные компоненты пути и, наконец, преобразовывая его обратно в форматированную строку C.

Это полезно для получения формата ведения журнала, который более читабельен, заменяя (например) такой журнал:

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

с:

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

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

Он содержит некоторые элементы времени выполнения и в этом смысле не полностью соответствует этому вопросу, но, вероятно, подходит для большинства условий.

  • 0
    Не могли бы вы более подробно изложить свой ответ, добавив немного больше описания предлагаемого вами решения?
  • 0
    @abarisone, надеюсь, это прояснит ответ. Если вы хотите, чтобы я уточнил больше по любому аспекту, пожалуйста, дайте мне знать.
0

Мне нравится @Chetan Reddy answer, в котором предлагается использовать static_assert() в выражении оператора для принудительного вызова времени компиляции для поиска последней косой черты, что позволяет избежать времени выполнения накладные расходы.

Однако выражения операторов являются нестандартным расширением и не поддерживаются повсеместно. Например, я не смог скомпилировать код из этого ответа в Visual Studio 2017 (предположим, MSVС++ 14.1).

Вместо этого, почему бы не использовать шаблон с целым параметром, например:

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

Определив такой шаблон, мы можем использовать его с функцией basename_index() из @Chetan Reddy answer:

require_at_compile_time<basename_index(__FILE__)>::value

Это гарантирует, что basename_index(__FILE__) на самом деле будет вызываться во время компиляции, так как это, когда аргумент шаблона должен быть известен.

При этом полный код для, пусть называет его JUST_FILENAME, макрос, оценивая только компонент имени файла __FILE__, будет выглядеть так:

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)

Я украл basename_index() почти дословно из ранее упомянутого ответа, за исключением того, что я добавил проверку для разделителя обратной косой черты под Windows.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню