__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条回答
Rolldiameter
2楼-- · 2020-02-04 07:28

you might want to try the __BASE_FILE__ macro. This page describes a lot of macros which gcc supports.

查看更多
闹够了就滚
3楼-- · 2020-02-04 07:35

Another possible approach when using CMake is to add a custom preprocessor definition that directly uses make's automatic variables (at the cost of some arguably ugly escaping):

add_definitions(-D__FILENAME__=\\"$\(<F\)\\")

Or, if you're using 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)

(Otherwise CMake will over-escape things.)

Here, we take advantage of the fact make substitutes $(<F) with the source file name without leading components and this should show up as -D__FILENAME__=\"MyFile.cpp\" in the executed compiler command.

(While make's documentation recommends using $(notdir path $<) instead, not having whitespace in the added definition seems to please CMake better.)

You can then use __FILENAME__ in your source code like you'd use __FILE__. For compatibility purposes you may want to add a safe fallback:

#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
查看更多
冷血范
4楼-- · 2020-02-04 07:36

There is currently no way of doing full string processing at compile time (the maximum we can work with in templates are the weird four-character-literals).

Why not simply save the processed name statically, e.g.:

namespace 
{
  const std::string& thisFile() 
  {
      static const std::string s(prepocessFileName(__FILE__));
      return s;
  }
}

This way you are only doing the work once per file. Of course you can also wrap this into a macro etc.

查看更多
放我归山
5楼-- · 2020-02-04 07:39

In projects using CMake to drive the build process, you can use a macro like this to implement a portable version that works on any compiler or platform. Though personally I pity you if you must use something other than 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()

Then to use the macro, just call it with the name of the CMake target:

define_file_basename_for_sources(myapplication)
查看更多
够拽才男人
6楼-- · 2020-02-04 07:44

I like @Chetan Reddy's answer, which suggests using static_assert() in a statement expression to force a compile time call to function finding the last slash, thus avoiding runtime overhead.

However, statement expressions are a non-standard extension and are not universally supported. For instance, I was unable to compile the code from that answer under Visual Studio 2017 (MSVC++ 14.1, I believe).

Instead, why not use a template with integer parameter, such as:

template <int Value>
struct require_at_compile_time
{
    static constexpr const int value = Value;
};

Having defined such a template, we can use it with basename_index() function from @Chetan Reddy's answer:

require_at_compile_time<basename_index(__FILE__)>::value

This ensures that basename_index(__FILE__) will in fact be called at compile time, since that's when the template argument must be known.

With this, the complete code for, let's call it JUST_FILENAME, macro, evaluating to just the filename component of __FILE__ would look like this:

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)

I've stolen basename_index() almost verbatim from the previously mentioned answer, except I added a check for Windows-specific backslash separator.

查看更多
Anthone
7楼-- · 2020-02-04 07:44

For Objective-C the following macro provides a CString which can replace the __FILE__ macro, but omitting the initial path components.

#define __BASENAME__ [[[NSString stringWithCString:__FILE__              \
                                        encoding:NSUTF8StringEncoding]   \
                                                    lastPathComponent]   \
                            cStringUsingEncoding:NSUTF8StringEncoding]   

That is to say it converts: /path/to/source/sourcefile.m into: sourcefile.m

It works by taking the ouptput of the __FILE__ macro (which is a C-formatted, null terminated string), converting it to an Objective-C string object, then stripping out the initial path components and finally converting it back into a C formatted string.

This is useful to get logging format which is more readable, replacing, (for example) a logging macro like this:

#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__)

It does contain some runtime elements, and in that sense does not fully comply with the question, but it is probably appropriate for most circumstances.

查看更多
登录 后发表回答