__FILE__ манипуляция макросами во время компиляции

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

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

Ответ 1

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

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

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

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

Ответ 2

Используя С++ 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__))

В этой версии без stmt-expr gcc 4.7 и 4.8 вызывают basename_index во время выполнения, поэтому вам лучше использовать версию stmt-expr с gcc. ICC 14 производит оптимальный код для обеих версий. ICC13 не может скомпилировать версию stmt-expr и создает неоптимальный код для версии без 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;
}

Ответ 3

В проектах, использующих 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)

Ответ 5

Другой метод С++ 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 будет выполняться во время компиляции, если это возможно, в противном случае оно будет отвисеть от выполнения команд выполнения.

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

Ответ 6

Другим возможным подходом при использовании 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

Ответ 7

Мне нравится @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.

Ответ 8

Для 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__)

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