Одна из проблем, которые я испытывал при переносе некоторых вещей из Solaris в Linux, заключается в том, что компилятор Solaris расширяет макрос __FILE__
во время предварительной обработки имени файла (например, MyFile.cpp), тогда как gcc в Linux расширяется до полного путь (например,/home/user/MyFile.cpp). Это можно легко разрешить с помощью basename(), но.... если вы используете его много, то все те обращения к basename() должны складываться, не так ли?
Вот вопрос. Есть ли способ использования шаблонов и статического метапрограммирования, для запуска basename() или подобного во время компиляции? Поскольку __FILE__
является постоянным и известен во время компиляции, это может сделать его проще. Как вы думаете? Это можно сделать?
В настоящее время нет способа выполнить полную обработку строк во время компиляции (максимум, с которым мы можем работать в шаблонах, - это странные четырехзначные литеры).
Почему бы просто не просто сохранить обработанное имя статически, например:
namespace
{
const std::string& thisFile()
{
static const std::string s(prepocessFileName(__FILE__));
return s;
}
}
Таким образом, вы выполняете только один раз для каждого файла. Конечно, вы можете также обернуть это в макрос и т.д.
Используя С++ 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;
}
basename_index()
из этого ответа.
В проектах, использующих 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)
вы можете попробовать макрос __BASE_FILE__
. Эта страница описывает много макросов, поддерживаемых gcc.
Другой метод С++ 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
будет выполняться во время компиляции, если это возможно, в противном случае оно будет отвисеть от выполнения команд выполнения.
Алгоритм немного отличается тем, что находит конец строки и затем работает назад, чтобы найти последнюю косую черту. Это, вероятно, медленнее, чем другой ответ, но поскольку он предназначен для выполнения во время компиляции, это не должно быть проблемой.
Другим возможным подходом при использовании 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
Для 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__)
Он содержит некоторые элементы времени выполнения и в этом смысле не полностью соответствует этому вопросу, но, вероятно, подходит для большинства условий.
Мне нравится @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.
__FILE__
расширяется до имени файла, указанного в командной строке, которое может быть либо абсолютным, либо относительным. Разница скорее всего в Makefile.__BASE_FILE__
, расширение gcc, отличается только тем, что оно дает самое внешнее имя файла, а не что-либо#include
d.