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.

jwm
  • 1,504
  • 1
  • 14
  • 29
  • 1
    I think extending the `std` namespace is undefined behavior. https://stackoverflow.com/questions/37541022/what-are-the-reasons-that-extending-the-std-namespace-is-considered-undefined-be – MFisherKDX Feb 01 '18 at 22:52
  • @MFisherKDX 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. – jwm Feb 01 '18 at 22:54
  • 1
    I don't suppose `using std::filesystem = std::experimental::filesystem::v1` works? – Mark Ransom Feb 01 '18 at 22:59
  • 1
    @jwm .. ok, I understand. Can you cast the function first like in this example? https://stackoverflow.com/questions/30393285/stdfunction-fails-to-distinguish-overloaded-functions – MFisherKDX Feb 01 '18 at 23:02
  • Got it ... had to dig a bit deeper in the answers, but you pointed me to the right place – jwm Feb 01 '18 at 23:24
  • 2
    @MarkRansom Would be `namespace std { namespace filesystem = experimental::filesystem::v1; }` but I like the idea. – Sebastian Redl Feb 01 '18 at 23:54

1 Answers1

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

jwm
  • 1,504
  • 1
  • 14
  • 29