__FILE__ macro manipulation handling at compile ti

2020-02-04 07:14发布

One of the issues I have had in porting some stuff from Solaris to Linux is that the Solaris compiler expands the macro __FILE__ during preprocessing to the file name (e.g. MyFile.cpp) whereas gcc on Linux expandeds out to the full path (e.g. /home/user/MyFile.cpp). This can be reasonably easily resolved using basename() but....if you're using it a lot, then all those calls to basename() have got to add up, right?

Here's the question. Is there a way using templates and static metaprogramming, to run basename() or similar at compile time? Since __FILE__ is constant and known at compile time this might make it easier. What do you think? Can it be done?

8条回答
干净又极端
2楼-- · 2020-02-04 07:50

Using C++11, you have a couple of options. Let's first define:

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)
     ;
}

If your compiler supports statement expressions, and you want to be sure that the basename computation is being done at compile-time, you can do this:

// 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;})

If your compiler doesn't support statement expressions, you can use this version:

// non stmt-expr version
#define __FILELINE__ (__FILE__ ":" STRINGIZE(__LINE__) ": " + basename_index(__FILE__))

With this non stmt-expr version, gcc 4.7 and 4.8 call basename_index at run-time, so you're better off using the stmt-expr version with gcc. ICC 14 produces optimal code for both versions. ICC13 can't compile the stmt-expr version, and produces suboptimal code for the non stmt-expr version.

Just for completeness, here's the code all in one place:

#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;
}
查看更多
劳资没心,怎么记你
3楼-- · 2020-02-04 07:50

Another C++11 constexpr method is as follows:

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));
}

The usage is pretty simple also:

std::cout << pathlast(__FILE__) << "\n";

The constexpr will be performed at compile-time if possible, otherwise it will fallback to run-time execution of the statements.

The algorithm is a little different in that it finds the end of the string and then works backwards to find the last slash. It is probably slower than the other answer but since it is intended to be executed at compile-time it shouldn't be an issue.

查看更多
登录 后发表回答