__FILE__ обработка макросов во время компиляции - PullRequest
22 голосов
/ 10 ноября 2009

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

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

Ответы [ 8 ]

20 голосов
/ 25 сентября 2013

Используя C ++ 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;
}
14 голосов
/ 16 января 2015

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

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

define_file_basename_for_sources(myapplication)
10 голосов
/ 10 ноября 2009

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

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

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

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

8 голосов
/ 10 ноября 2009

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

5 голосов
/ 28 апреля 2015

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

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

4 голосов
/ 30 октября 2017

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

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

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

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

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

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 разделителя обратной косой черты.

4 голосов
/ 27 октября 2016

Другой возможный подход при использовании 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
2 голосов
/ 17 апреля 2015

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

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

...