我将一些东西从Solaris迁移到Linux时遇到的一个问题是Solaris编译器在预处理期间将宏__FILE__
扩展为文件名(例如MyFile.cpp),而Linux上的gcc扩展为完整路径(例如/home/user/MyFile.cpp)。使用basename()可以很容易地解决这个问题但是......如果你经常使用它,那么对basename()的所有调用都必须加起来,对吧?
这是问题所在。有没有办法使用模板和静态元编程,在编译时运行basename()或类似的?由于__FILE__
是常量且在编译时已知,因此可能会更容易。你怎么看?可以吗?
答案 0 :(得分:20)
使用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,因此最好使用带有gcc的stmt-expr版本。 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;
}
答案 1 :(得分:13)
在使用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)
答案 2 :(得分:9)
目前无法在编译时进行完整的字符串处理(我们可以在模板中使用的最大值是奇怪的四字符文字)。
为什么不简单地静态保存处理过的名称,例如:
namespace
{
const std::string& thisFile()
{
static const std::string s(prepocessFileName(__FILE__));
return s;
}
}
这样你每个文件只做一次工作。当然你也可以将它包装成一个宏等。
答案 3 :(得分:8)
你可能想尝试__BASE_FILE__
宏。这个page描述了gcc支持的很多宏。
答案 4 :(得分:5)
另一种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
将在编译时执行,否则它将回退到语句的运行时执行。
算法略有不同,因为它找到了字符串的结尾,然后向后工作以找到最后一个斜杠。它可能比其他答案慢,但因为它打算在编译时执行它不应该是一个问题。
答案 5 :(得分:4)
使用CMake时另一种可能的方法是添加一个直接使用make
的{{3}}的自定义预处理器定义(代价是一些可以说是丑陋的逃避):
add_definitions(-D__FILENAME__=\\"$\(<F\)\\")
或者,如果您使用的是CMake&gt; = 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 automatic variables。)
在这里,我们利用事实make
将$(<F)
替换为源文件名而不使用前导组件,这应该在执行的编译器命令中显示为-D__FILENAME__=\"MyFile.cpp\"
。
(虽然make
的文档建议使用$(notdir path $<)
,但添加的定义中没有空格似乎可以让CM更好。)
然后,您可以在源代码中使用__FILENAME__
,就像使用__FILE__
一样。出于兼容性目的,您可能需要添加安全后备:
#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
答案 6 :(得分:4)
我喜欢@Chetan Reddy's answer,它建议在语句表达式中使用static_assert()
强制编译时调用函数查找最后一个斜杠,从而避免运行时开销。
但是,语句表达式是非标准扩展,并且不受普遍支持。例如,我无法在Visual Studio 2017下编译该答案的代码(MSVC ++ 14.1,我相信)。
相反,为什么不使用带有整数参数的模板,例如:
template <int Value>
struct require_at_compile_time
{
static constexpr const int value = Value;
};
定义了这样的模板之后,我们可以使用来自@Chetan Reddy的答案的basename_index()
函数:
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)
我几乎逐字地从previously mentioned answer偷了basename_index()
,除了我添加了对特定于Windows的反斜杠分隔符的检查。
答案 7 :(得分:2)
对于Objective-C,下面的宏提供了一个CString,可以替换__FILE__
宏,但省略了初始路径组件。
#define __BASENAME__ [[[NSString stringWithCString:__FILE__ \
encoding:NSUTF8StringEncoding] \
lastPathComponent] \
cStringUsingEncoding:NSUTF8StringEncoding]
也就是说它将/path/to/source/sourcefile.m
转换为:sourcefile.m
它的工作原理是获取__FILE__
宏的ouptput(这是一个C格式的,以空字符结尾的字符串),将其转换为Objective-C字符串对象,然后剥离出初始路径组件并最终转换它回到C格式的字符串。
这对于获取更具可读性的日志记录格式非常有用,可以替换(例如)像这样的日志记录宏:
#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), \
__FILE__, __LINE__, ##__VA_ARGS__)
with:
#define __BASENAME__ [[[NSString stringWithCString:__FILE__ \
encoding:NSUTF8StringEncoding] \
lastPathComponent] \
cStringUsingEncoding:NSUTF8StringEncoding]
#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), \
__BASENAME__, __LINE__, ##__VA_ARGS__)
它确实包含一些运行时元素,从这个意义上讲并不完全符合这个问题,但它可能适用于大多数情况。
答案 8 :(得分:0)
我已将 constexpr
版本压缩为一个递归函数,该函数查找最后一个斜杠并返回指向斜杠后字符的指针。编译时间很有趣。
constexpr const char* const fileFromPath(const char* const str, const char* const lastslash = nullptr) {
return *str ? fileFromPath(str + 1, ((*str == '/' || *str == '\\') ? str + 1 : (nullptr==lastslash?str:lastslash)) : (nullptr==lastslash?str:lastslash);
}