Sca*_*ark 23 c c++ templates metaprogramming
我在将一些东西从Solaris移植到Linux时遇到的一个问题是Solaris编译器__FILE__在预处理过程中将宏扩展为文件名(例如MyFile.cpp),而Linux上的gcc扩展到完整路径(例如/ home /用户/ MYFILE.CPP).使用basename()可以很容易地解决这个问题但是......如果你经常使用它,那么对basename()的所有调用都必须加起来,对吧?
这是问题所在.有没有办法使用模板和静态元编程,在编译时运行basename()或类似的?由于它__FILE__是常量且在编译时已知,因此可能更容易.你怎么看?可以吗?
小智 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)
;
}
Run Code Online (Sandbox Code Playgroud)
如果您的编译器支持语句表达式,并且您希望确保在编译时完成基本名称计算,则可以执行以下操作:
// 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;})
Run Code Online (Sandbox Code Playgroud)
如果您的编译器不支持语句表达式,则可以使用此版本:
// non stmt-expr version
#define __FILELINE__ (__FILE__ ":" STRINGIZE(__LINE__) ": " + basename_index(__FILE__))
Run Code Online (Sandbox Code Playgroud)
使用这个非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;
}
Run Code Online (Sandbox Code Playgroud)
Col*_*ett 17
在使用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})
# Add the FILE_BASENAME=filename compile definition to the list.
get_filename_component(basename "${sourcefile}" NAME)
# Set the updated compile definitions on the source file.
set_property(
SOURCE "${sourcefile}" APPEND
PROPERTY COMPILE_DEFINITIONS "FILE_BASENAME=\"${basename}\"")
endforeach()
endfunction()
Run Code Online (Sandbox Code Playgroud)
然后要使用宏,只需使用CMake目标的名称调用它:
define_file_basename_for_sources(myapplication)
Run Code Online (Sandbox Code Playgroud)
Geo*_*che 10
目前无法在编译时进行完整的字符串处理(我们可以在模板中使用的最大值是奇怪的四字符文字).
为什么不简单地静态保存处理过的名称,例如:
namespace
{
const std::string& thisFile()
{
static const std::string s(prepocessFileName(__FILE__));
return s;
}
}
Run Code Online (Sandbox Code Playgroud)
这样你每个文件只做一次工作.当然你也可以将它包装成一个宏等.
另一种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));
}
Run Code Online (Sandbox Code Playgroud)
用法也很简单:
std::cout << pathlast(__FILE__) << "\n";
Run Code Online (Sandbox Code Playgroud)
该constexpr会在编译时进行,如果可能的话,否则会退回到运行时的语句的执行。
该算法稍有不同,它找到字符串的结尾,然后向后工作以找到最后的斜杠。它可能比其他答案要慢,但是由于它打算在编译时执行,所以这不是问题。
使用 CMake 时的另一种可能的方法是添加直接使用 的自动变量的make自定义预处理器定义(以一些可以说是丑陋的转义为代价):
add_definitions(-D__FILENAME__=\\"$\(<F\)\\")
Run Code Online (Sandbox Code Playgroud)
或者,如果您使用的是 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)
Run Code Online (Sandbox Code Playgroud)
(否则 CMake会过度转义。)
在这里,我们利用了用源文件名make替换$(<F)而不带前导组件的事实,这应该显示-D__FILENAME__=\"MyFile.cpp\"在执行的编译器命令中。
(虽然make的文档建议使用$(notdir path $<),但在添加的定义中不使用空格似乎更能让 CMake 满意。)
然后,您可以__FILENAME__像使用 一样在源代码中使用__FILE__. 出于兼容性目的,您可能需要添加安全后备:
#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
Run Code Online (Sandbox Code Playgroud)
我喜欢@Chetan Reddy 的回答,它建议static_assert()在语句表达式中使用强制编译时调用查找最后一个斜杠的函数,从而避免运行时开销。
但是,语句表达式是一种非标准的扩展,并没有得到普遍支持。例如,我无法在 Visual Studio 2017(我相信是 MSVC++ 14.1)下编译该答案中的代码。
相反,为什么不使用带有整数参数的模板,例如:
template <int Value>
struct require_at_compile_time
{
static constexpr const int value = Value;
};
Run Code Online (Sandbox Code Playgroud)
定义了这样的模板后,我们可以将它与basename_index()@Chetan Reddy 的回答中的函数一起使用:
require_at_compile_time<basename_index(__FILE__)>::value
Run Code Online (Sandbox Code Playgroud)
这确保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)
Run Code Online (Sandbox Code Playgroud)
我basename_index()几乎逐字从前面提到的答案中窃取了内容,除了我添加了针对 Windows 特定反斜杠分隔符的检查。