10

I recently discovered that when using the __FILE__ predefined macro in MSVC (specifically 2013) that by default it will print relative paths for source files and absolute paths for header files.

As an example I have a VS project containing the following:

Solution
    Project
        Headers
            foo.h
        Sources
            main.cpp

Both main.cpp and foo.h are in the same directory on disk.

main.cpp:

#include <iostream>
#include <string>

#include "foo.h"

int main(int, char*[])
{
    std::cout << __FILE__ << std::endl;
    foo::bar();

    std::cout << "Press enter to exit";
    std::string str;
    std::getline(std::cin, str);

    return 0;
}

foo.h:

#ifndef FOO_H
#define FOO_H

#include <iosfwd>

class foo
{
public:
    static void bar()
    {
        std::cout << __FILE__ << std::endl;
    }
};

#endif

When I run the application (in release mode, with the default settings - compiling with /Zi and /FC is not defined) then the output is:

main.cpp
c:\users\<user>\documents\dev\solution\project\foo.h
Press enter to exit

I know I could probably pass in a base path and strip it out at runtime but I was wondering whether there was any way to change this behavior at compile time? Obviously defining /FC produces the opposite result and I cannot see anything else in the MSVC manual to control the display of header paths. I am thinking this might be a hardcoded behavior so that if the compiler is able to pick up two files called foo.h from different include paths that you can still distinguish between them or because it would be possible to have an include path unrelated to the base of the sources and displaying as relative would be messy.

tinman
  • 6,348
  • 1
  • 30
  • 43
  • 1
    When calling the compiler, is the include path (/I paramater) specified as absolute or as relative path? If absolute, does the behaviour change if you pass a relative path? – sb9 Jan 10 '17 at 12:43
  • 1
    Also take a look at http://stackoverflow.com/questions/8487986/file-macro-shows-full-path – sb9 Jan 10 '17 at 13:15
  • @sb9: Thanks for linking the other Q. the include path is not set by the IDE since the header is in the same path as the source. Building from the command line and trying to set the /I parameter to absolute or relative makes no difference - it always prints the absolute path. – tinman Jan 12 '17 at 15:58
  • 1
    duplicates: [Relative path for `__FILE__` and PDB](https://stackoverflow.com/q/60929749/995714), [How do I configure MSVC to show relative path for header files using __FILE__?](https://stackoverflow.com/q/41567178/995714). But since this is C++ it can be solved simply by [making a `constexpr` function to parse the path](https://stackoverflow.com/q/31050113/995714) – phuclv Dec 13 '22 at 05:01
  • 2
    For VS2022, the behavior is still the same. There is a related [feature request on developercommunity](https://developercommunity.visualstudio.com/t/map-file-to-a-relative-path/1393881). Also, C++20 `std::source_location::current().file_name()` behaves the same as `__FILE__`. So I guess the only solution is to extract the filename by [post-processing `__FILE__` at compile time](https://stackoverflow.com/a/38237385/3740047). – Sedenion Dec 14 '22 at 20:18
  • The compiler just echoes back what it got from its command line. So if you edit the .vcxproj file and update `'s` Include attribute to the full path then the macro also spits out a full path. This is not terribly practical. – Hans Passant Dec 16 '22 at 00:40
  • @HansPassant: This is about headers. Those are not in ``. – user541686 Dec 16 '22 at 01:23
  • Also, it is not about getting absolute paths, which is possible via `/FC`, but about getting relative paths always. – Sedenion Dec 16 '22 at 06:36

1 Answers1

0

I'll post my own solution, which is imperfect but the closest workaround I know of.

Every file this code is included in will strip off the PROJECT_DIR prefix from __FILE__ when assert is called, and thus the binary will not contain that prefix.

A better solution would probably wish to add ..\ prefixes as necessary, handle / as well, and possibly preserve any absolute paths that are on a different volume, but I'm not doing any of those here.

// Add this to your .vcxproj:
// <PreprocessorDefinitions>PROJECT_DIR="$(ProjectDir.Replace('\', '\\').TrimEnd('\'))";%(PreprocessorDefinitions)</PreprocessorDefinitions>

#include <cassert>
#include <type_traits>

#ifdef PROJECT_DIR
template<class Char1, class Char2>
constexpr size_t path_imismatch(Char1 const a[], Char2 const b[])
{
    size_t r = 0;
    size_t i = 0;
    for (;;)
    {
        Char1 ch1 = a[i];
        Char2 ch2 = b[i];
        bool const finished = ch1 == Char1() || ch2 == Char2() || !(ch1 == ch2);
        if (ch2 == Char2()) { ch2 = '\\'; }
        bool const isdirsep = ch1 == '\\' && ch1 == ch2;
        if (isdirsep && ch1 == ch2) { r = i + 1; }
        if (finished) { break; }
        ++i;
    }
    return r;
}
#define FILENAME(File, Root) ([]() { typedef std::decay_t<decltype(*(File))> _FileNameChar; constexpr _FileNameChar const f[] = File; enum : size_t { O = path_imismatch(f, Root), N = sizeof(f) / sizeof(*f) - O }; constexpr std::array<_FileNameChar, N> const result = []() { std::array<_FileNameChar, N> r = {}; for (size_t i = 0; i < N; ++i) { r[i] = f[O + i]; } return r; }(); return std::integral_constant<std::remove_const_t<decltype(result)>, result>(); }().value.data())
#define _wassert(Source, File, Line) (_wassert)(Source, FILENAME(File, _CRT_WIDE(PROJECT_DIR)), Line)
#endif
user541686
  • 205,094
  • 128
  • 528
  • 886