C++ __编译时的文件\宏操作处理
我在将一些东西从Solaris移植到Linux时遇到的一个问题是,Solaris编译器在预处理过程中将宏C++ __编译时的文件\宏操作处理,c++,c,templates,metaprogramming,C++,C,Templates,Metaprogramming,我在将一些东西从Solaris移植到Linux时遇到的一个问题是,Solaris编译器在预处理过程中将宏\uuuuu FILE\uuuu扩展到文件名(例如MyFile.cpp),而Linux上的gcc扩展到完整路径(例如/home/user/MyFile.cpp)。使用basename()可以很容易地解决这个问题,但是……如果您经常使用它,那么所有对basename()的调用都必须加起来,对吗 问题是。有没有一种方法可以使用模板和静态元编程在编译时运行basename()或类似的程序?由于\uu
\uuuuu FILE\uuuu
扩展到文件名(例如MyFile.cpp),而Linux上的gcc扩展到完整路径(例如/home/user/MyFile.cpp)。使用basename()可以很容易地解决这个问题,但是……如果您经常使用它,那么所有对basename()的调用都必须加起来,对吗
问题是。有没有一种方法可以使用模板和静态元编程在编译时运行basename()或类似的程序?由于
\uuuu文件\uuuu
是常量,并且在编译时已知,因此这可能会使编译更容易。你怎么认为?可以这样做吗?目前没有办法在编译时进行完整的字符串处理(我们在模板中最多可以处理怪异的四个字符的文本)
为什么不简单地静态保存处理过的名称,例如:
namespace
{
const std::string& thisFile()
{
static const std::string s(prepocessFileName(__FILE__));
return s;
}
}
这样,每个文件只做一次工作。当然,您也可以将其包装到宏等中。您可能希望尝试使用
\uuuu BASE\u文件\uuuu
宏。这描述了gcc支持的许多宏。使用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)
;
}
如果您的编译器支持语句表达式,并且希望确保在编译时完成basename计算,则可以执行以下操作:
// 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_索引,因此最好在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;
}
#包括
#包括
constexpr int32\u t basename\u索引(const char*const path,const int32\u t index=0,const int32\u t slash\u index=-1)
{
返回路径[索引]
?(路径[索引]=='/'
basename_索引(路径,索引+1,索引)
:basename\u索引(路径、索引+1、斜杠\u索引)
)
:(斜杠索引+1)
;
}
#定义细部尺寸(x)#x
#定义STRINGIZE(x)STRINGIZE_详图(x)
#定义u_文件线___;({static const int32_t basename_idx=basename_索引(_文件_;)\
静态断言(basename\u idx>=0,“编译时basename”)\
__文件“:”字符串化(“+basename\u idx;}”)
int main(){
std::cout在使用CMake驱动构建过程的项目中,您可以使用这样的宏来实现在任何编译器或平台上都能工作的可移植版本。尽管我个人很遗憾您必须使用gcc以外的东西……)
然后,要使用宏,只需使用CMake目标的名称调用它:
define_file_basename_for_sources(myapplication)
对于Objective-C,以下宏提供了一个CString,它可以替换\uuuu文件\uuuu
宏,但省略了初始路径组件
#define __BASENAME__ [[[NSString stringWithCString:__FILE__ \
encoding:NSUTF8StringEncoding] \
lastPathComponent] \
cStringUsingEncoding:NSUTF8StringEncoding]
也就是说,它将:/path/to/source/sourcefile.m
转换为:sourcefile.m
它的工作原理是将\uuuu FILE\uuu
宏(一个C格式、以null结尾的字符串)的输出转换为Objective-C字符串对象,然后剥离初始路径组件,最后将其转换回C格式字符串
这对于获取可读性更高的日志记录格式非常有用,可以替换(例如)这样的日志记录宏:
#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), \
__FILE__, __LINE__, ##__VA_ARGS__)
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)
与:
#define __BASENAME__ [[[NSString stringWithCString:__FILE__ \
encoding:NSUTF8StringEncoding] \
lastPathComponent] \
cStringUsingEncoding:NSUTF8StringEncoding]
#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), \
__BASENAME__, __LINE__, ##__VA_ARGS__)
它确实包含一些运行时元素,从这个意义上讲并不完全符合这个问题,但它可能适用于大多数情况。另一个C++11constexpr
方法如下:
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";
std::cout使用CMake时的另一种可能方法是添加一个直接使用make
的自定义预处理器定义(代价是一些不合理的转义):
我喜欢,这建议在语句表达式中使用static\u assert()
,强制编译时调用函数查找最后一个斜杠,从而避免运行时开销
但是,语句表达式是一种非标准的扩展,不受普遍支持。例如,我无法在Visual Studio 2017(我相信是MSVC++14.1)下编译来自该答案的代码
相反,为什么不使用带有整数参数的模板,例如:
template <int Value>
struct require_at_compile_time
{
static constexpr const int value = Value;
};
这确保了basename\u索引(\uuu文件\uu)
实际上将在编译时被调用,因为此时必须知道模板参数
有了它,宏的完整代码(我们称之为JUST_FILENAME
)计算为\uuu FILE\uuu
的文件名组件,如下所示:
#define MyLog(fmt, ...) MyLog((@"E %s [Line %d] " fmt), \
__FILE__, __LINE__, ##__VA_ARGS__)
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)
constexpr int32\u t basename\u索引(
const char*const path,const int32_t index=0,const int32_t slash_index=-1
)
{
返回路径[索引]
((路径[索引]='/'| |路径[索引]=='\\')/(见下文)
basename_索引(路径,索引+1,索引)
:basename\u索引(路径、索引+1、斜杠\u索引)
)
:(斜杠索引+1)
;
}
模板
结构在编译时需要
{
静态constexpr const int32_t value=值;
};
#仅定义文件名(uuu FILE_uuu+require_uat_compile_time::value)
我几乎一字不差地从中窃取了basename_index()
,只是我添加了一个检查Windows特定反斜杠分隔符的选项。我将constepr
版本压缩为一个递归函数,该函数查找最后一个斜杠并返回指向斜杠后字符的指针。编译时很有趣
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);
}
非常好的一点。如果这导致Solaris下的编译错误,并且您必须同时支持这两个错误,请添加ifdef以检查BASE_文件,如果BASE_文件不存在,则使用FILE。实际上这不是一个很好的观点。Solaris上的BASE_文件仍然包括
require_at_compile_time<basename_index(__FILE__)>::value
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)
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);
}