编译时__FILE__宏操作处理

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)

  • 不幸的是,这不能很好地替代 `__FILE__`,因为它只报告编译单元源文件的文件名,而不是你可能将实际代码放入的任何头文件(与 __LINE__ 相关)。 (2认同)

Geo*_*che 10

目前无法在编译时进行完整的字符串处理(我们可以在模板中使用的最大值是奇怪的四字符文字).

为什么不简单地静态保存处理过的名称,例如:

namespace 
{
  const std::string& thisFile() 
  {
      static const std::string s(prepocessFileName(__FILE__));
      return s;
  }
}
Run Code Online (Sandbox Code Playgroud)

这样你每个文件只做一次工作.当然你也可以将它包装成一个宏等.


And*_*ith 8

你可能想尝试__BASE_FILE__宏.这个页面描述了gcc支持的很多宏.

  • 优点.如果这导致Solaris下的编译错误,并且您必须同时支持两者,请添加ifdef以检查BASE_FILE,如果BASE_FILE不存在则使用__FILE__. (2认同)
  • 其实这不是一个很好的点。__BASE_FILE__ 在solaris 上仍然包含文件的路径,而不仅仅是它的名称组件。 (2认同)

Jon*_*ett 6

另一种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会在编译时进行,如果可能的话,否则会退回到运行时的语句的执行。

该算法稍有不同,它找到字符串的结尾,然后向后工作以找到最后的斜杠。它可能比其他答案要慢,但是由于它打算在编译时执行,所以这不是问题。

  • 我们可以通过将函数的结果存储在 **`constexpr`** 变量中作为 `constexpr const char* myExpression = pathlast(__FILE__); 来**强制**并**保证**在编译时对其进行评估。std::cout &lt;&lt; myExpression &lt;&lt; "\n";`。来源:[在编译时计算 C 字符串的长度。这真的是 constexpr 吗?](/sf/ask/1812354911/) (2认同)

Kef*_*fen 6

使用 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)


Ter*_*ass 6

我喜欢@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 特定反斜杠分隔符的检查。