How should I make std::filesystem appear standards-conforming to Visual Studio 2015
Asked Answered
T

1

6

I have a project that is currently locked into Visual Studio 2015. However, I want to write code that is as standards-conforming as possible.

I want to use std::filesystem but it didn't make it into the standard until C++-17. Fortunately, just about everything is available, just in the std::experimental::filesystem::v1 namespace. I'm not a fan of blanket using directives; I prefer to scope things fully to make it clear where the thing comes from. So I'm not going to just put in a global using statement. Some magic is required to convince the compiler to do what I want.

This was my first attempt:

#include <filesystem>
#if defined(_MSC_VER) && _MSC_VER <= 1900 // VS 2015
namespace std {
    namespace filesystem {
        using path = std::experimental::filesystem::v1::path;
    }
}
#endif

That works pretty well, and std::filesystem::path is now accessible. I've tested creating and using path objects and it works.

As I move forward, I know I'm going to need more stuff. I wondered if there might be a way to just bring in the whole thing:

namespace std {
    namespace filesystem {
        using std::experimental::filesystem::v1;
    }
}

That seemed like a step backwards. Nothing appears to be visible. In hindsight, I guess it makes sense, since the scope of the using statement ends with the closing brace on the next line.

Next, I want to get a directory_entry. The same technique seems to work

namespace std {
    namespace filesystem {
        using directory_entry = std::experimental::filesystem::v1::directory_entry;
    }
}

Again, the compiler seems happy.

Now, I want to use std::directory::create_directories. However, this is a function, not a class, so the same technique won't work.

I thought that std::function might be tailor made for this, but I'm not having any luck. I tried

namespace std {
    namespace filesystem {
        function<bool(path)> create_directories = std::experimental::filesystem::v1::create_directories;
    }
}

and the compiler says

Error   C2440   'initializing': cannot convert from 'overloaded-function' to 'std::function<bool (std::filesystem::path)>'

There are two overloads of the function (one takes a second argument to return an error code rather than throwing an exception).

I'm stuck. This has to be possible but my C++-foo is weak.

Trapan answered 1/2, 2018 at 22:42 Comment(6)
I think extending the std namespace is undefined behavior. #37541522Scalpel
@Scalpel I understand that, and undefined means undefined. But if you substitute foo for std the behavior would be the same. And in this case, I consider extending it justified, since the symbols I'm adding are in a later version of the standard.Trapan
I don't suppose using std::filesystem = std::experimental::filesystem::v1 works?Cinthiacintron
@Trapan .. ok, I understand. Can you cast the function first like in this example? #30393785Scalpel
Got it ... had to dig a bit deeper in the answers, but you pointed me to the right placeTrapan
@MarkRansom Would be namespace std { namespace filesystem = experimental::filesystem::v1; } but I like the idea.Harveyharvie
T
5

The answer lies in the error message, and the help required to instantiate std::function for an overloaded method. Thanks to MFisherKDX for pointing me here and to W.F. for the answer there that works.

Ignoring the question of whether it is legal, moral, or in good taste to extend the standard namespace (because in this case I believe it is at least 2 of the 3), here's my fully commented work-around:

#if defined(_MSC_VER) && _MSC_VER <= 1900
// Visual Studio 2015 work-around ... 
// std::filesystem was incorporated into C++-17 (which is obviously after VS
// 2015 was released). However, Microsoft implemented the draft standard in
// the std::exerimental namespace. To avoid nasty ripple effects when the
// compiler is updated, make it look like the standard here
#include <functional>
namespace std {
  namespace filesystem {
    using directory_entry = std::experimental::filesystem::v1::directory_entry;
    using directory_iterator = std::experimental::filesystem::v1::directory_iterator;
    function<bool(path const&)> create_directories = 
        static_cast<bool(*)(path const&)>(
            std::experimental::filesystem::v1::create_directories);
  }
}
#endif

UPDATE: Sebastian had the simplest solution.

#if defined(_MSC_VER) && _MSC_VER <= 1900
// Visual Studio 2015 work-around ... 
// std::filesystem was incorporated into C++-17 (which is obviously after VS
// 2015 was released). However, Microsoft implemented the draft standard in
// the std::exerimental namespace. To avoid nasty ripple effects when the
// compiler is updated, make it look like the standard here
namespace std {
  namespace filesystem = experimental::filesystem::v1;
}
#endif

By the way, gcc 7.3 requires nearly exactly the same work-around, except you can't

#include <filesystem>

but must

#include <experimental/filesystem>

instead

Trapan answered 1/2, 2018 at 23:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.