__FILE__ macro manipulation handling at compile time
Asked Answered
S

9

25

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?

Salerno answered 10/11, 2009 at 8:19 Comment(1)
A few quick experiments show that __FILE__ expands to the file name as given on the command line, which could be either absolute or relative. The difference is likely in the Makefile. __BASE_FILE__, a gcc extension, differs only in that it yields the outermost file name rather than anything #included.Dorie
A
20

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;
}
Aquino answered 25/9, 2013 at 12:19 Comment(1)
The idea with static_assert() in a statement expression is a very nice one and works perfectly with GCC, however, since statement expressions are a non-standard extension, as you said, this solution is not universal. I think there's a way to force a compile time evaluation without relying on statement expressions, using a template with integer parameter. I posted an answer describing it, which uses basename_index() from this answer.Karbala
G
23

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 the fool who 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.
#
# Note that in header files this is not consistent with
# __FILE__ and __LINE__ since FILE_BASENAME will be the
# compilation unit source file name (.c/.cpp).
#
# 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)
Ghee answered 16/1, 2015 at 18:28 Comment(6)
The op doesn't mention cmake but for people who are after a cmake solution, that's such a marvellous answer.Unsociable
I've suggested an edit to use set_property(APPEND) rather than get_property() and list(APPEND) manually.Towny
Unfortunately this is not great as a replacement for __FILE__ because it just reports the filename of the compilation unit sourcefile, NOT whatever header you might have put the actual code in (which LINE is in relation to).Deuteranopia
@StevenLu that's a good point. It is an imperfect solution. So its usefulness depends on how you use it. When you are using it for assertions in your implementation file, it works as intended.Ghee
I begrudgingly went with the strrchr kludge. We've been promised with compile time string manipulation for a while. Where is it? Argh.Deuteranopia
I'm dumb. there are multiple serviceable constexpr implementations right here on this page. 🤦Deuteranopia
A
20

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;
}
Aquino answered 25/9, 2013 at 12:19 Comment(1)
The idea with static_assert() in a statement expression is a very nice one and works perfectly with GCC, however, since statement expressions are a non-standard extension, as you said, this solution is not universal. I think there's a way to force a compile time evaluation without relying on statement expressions, using a template with integer parameter. I posted an answer describing it, which uses basename_index() from this answer.Karbala
D
10

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.

Dissever answered 10/11, 2009 at 8:24 Comment(0)
D
9

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

Disposed answered 10/11, 2009 at 8:25 Comment(3)
Excellent point. If that results in compile errors under Solaris and you have to support both, add ifdefs to check for BASE_FILE and use FILE if BASE_FILE is not present.Andrien
Actually this isn't a very good point. BASE_FILE on solaris still includes the path of the file, not just it's name component.Salerno
See mail-archive.com/[email protected]/msg00556.html: BASE_FILE is the main file you're compiling, not the included header you're currently in.Jonajonah
T
8

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.

Twilley answered 28/4, 2015 at 13:33 Comment(4)
Good idea, but it does not work properly. || should be replaced to && for fixing it.Hawking
The only answer which actually works with Microsoft Visual Studio compiler!Maudemaudie
We can force and garante this to be evaluated at compile time by storing the result of the function in a constexpr variable as constexpr const char* myExpression = pathlast(__FILE__); std::cout << myExpression << "\n";. Source: Computing length of a C string at compile time. Is this really a constexpr?Maudemaudie
THIS RIGHT HERE IS THE DROID YOU ARE LOOKING FORDeuteranopia
K
8

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.

Karbala answered 30/10, 2017 at 16:29 Comment(0)
G
6

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
Gusher answered 27/10, 2016 at 12:52 Comment(2)
With one toolchain this worked well, but another one passed cmake_pch.hxx.gch as a __FILENAME__ for all files. Probably, because it has been added this file to the beginning of make's sources. I ended up with using add_definitions(-D__FILENAME__=\\"$\(subst .o,,$\(@F\)\)\\") that works for mine both cases. It takes target name, that is conventionally source_name.cpp.o, and removes .oChadd
I had to use two $ characters with cmake 3.24: add_definitions(-D__FILENAME__=\\"$$\(<F\)\\"), but it works, thank you!!Manualmanubrium
S
3

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.

Sexpartite answered 17/4, 2015 at 10:53 Comment(2)
Could you please elaborate more your answer adding a little more description about the solution you provide?Research
@abarisone, hope that clarifies the answer. If you would like me to elaborate more on any aspect, please let me know.Sexpartite
L
0

I've compressed the constexpr version down to one recursive function that finds the last slash and returns a pointer to the character after the slash. Compile time fun.

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);
}
Longmire answered 6/2, 2021 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.