diff --git a/README.md b/README.md index 8691969..d270f17 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,11 @@ to the expected behavior. ## Release Notes +### v1.3.0 (wip) + +* Initial support for C++20 - `ghc::filesystem` now supports interfacing with + `char8_t` based `std::u8string`, when compiled in C++20 mode. + ### [v1.2.10](https://github.com/gulrak/filesystem/releases/tag/v1.2.10) * The Visual Studio 2019 compiler, GCC 9.2 and Clang 9.0 where added to the diff --git a/cmake/GhcHelper.cmake b/cmake/GhcHelper.cmake index edbbca7..1772523 100644 --- a/cmake/GhcHelper.cmake +++ b/cmake/GhcHelper.cmake @@ -31,6 +31,11 @@ if (CMAKE_COMPILER_IS_GNUCXX AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 8.0 O endif() target_compile_definitions(${targetName} PRIVATE USE_STD_FS) endif() +if (CMAKE_COMPILER_IS_GNUCXX AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0)) + add_executable(${targetName}_20 ${ARGN}) + set_property(TARGET ${targetName}_20 PROPERTY CXX_STANDARD 20) + target_compile_definitions(${targetName}_20 PRIVATE USE_STD_FS) +endif() if(CMAKE_CXX_COMPILER_ID MATCHES MSVC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 19.15 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.15)) add_executable(${targetName} ${ARGN}) @@ -41,3 +46,14 @@ if(CMAKE_CXX_COMPILER_ID MATCHES MSVC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQ endif() endmacro() + +macro(AddExecutableWithCppStd targetName cppStd) + if ((CMAKE_COMPILER_IS_GNUCXX AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0)) OR + ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 9.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0))) + add_executable(${targetName} ${ARGN}) + set_property(TARGET ${targetName} PROPERTY CXX_STANDARD ${cppStd}) + set_property(TARGET ${targetName} PROPERTY CXX_STANDARD_REQUIRED ON) + target_link_libraries(${targetName} ghc_filesystem) + target_compile_options(${targetName} PRIVATE -fchar8_t -Wall -Wextra -Wshadow -Wconversion -Wsign-conversion -Wpedantic -Werror) + endif() +endmacro() diff --git a/include/ghc/filesystem.hpp b/include/ghc/filesystem.hpp index d1e8b54..be1a160 100644 --- a/include/ghc/filesystem.hpp +++ b/include/ghc/filesystem.hpp @@ -260,9 +260,15 @@ public: path>::type; template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value, path>::type; +#else +#ifdef __cpp_lib_char8_t + template + using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value + || std::is_same::type>::value || std::is_same::type>::value, path>::type; #else template using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; +#endif template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; #endif @@ -335,7 +341,11 @@ public: std::basic_string string(const Allocator& a = Allocator()) const; std::string string() const; std::wstring wstring() const; +#ifdef __cpp_lib_char8_t + std::u8string u8string() const; +#else std::string u8string() const; +#endif std::u16string u16string() const; std::u32string u32string() const; @@ -344,7 +354,11 @@ public: std::basic_string generic_string(const Allocator& a = Allocator()) const; const std::string& generic_string() const; // this is different from the standard, that returns by value std::wstring generic_wstring() const; +#ifdef __cpp_lib_char8_t + std::u8string generic_u8string() const; +#else std::string generic_u8string() const; +#endif std::u16string generic_u16string() const; std::u32string generic_u32string() const; @@ -1297,12 +1311,20 @@ GHC_INLINE bool validUtf8(const std::string& utf8String) namespace detail { -template ::type* = nullptr> +template ::value>::type* = nullptr> inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { return StringType(utf8String.begin(), utf8String.end(), alloc); } +#ifdef __cpp_lib_char8_t +template ::value>::type* = nullptr> +inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(reinterpret_cast(utf8String.data()), utf8String.length(), alloc); +} +#endif + template ::type* = nullptr> inline StringType fromUtf8(const std::string& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { @@ -1376,12 +1398,20 @@ inline StringType fromUtf8(const std::string& utf8String, const typename StringT return result; } -template ::type size = 1> +template ::value, int>::type size = 1> inline std::string toUtf8(const std::basic_string& unicodeString) { return std::string(unicodeString.begin(), unicodeString.end()); } +#ifdef __cpp_lib_char8_t +template ::value, int>::type size = 1> +inline std::string toUtf8(const std::basic_string& unicodeString) +{ + return std::string(reinterpret_cast(unicodeString.data()), unicodeString.length()); +} +#endif + template ::type size = 2> inline std::string toUtf8(const std::basic_string& unicodeString) { @@ -2400,10 +2430,17 @@ GHC_INLINE std::wstring path::wstring() const #endif } +#ifdef __cpp_lib_char8_t +GHC_INLINE std::u8string path::u8string() const +{ + return detail::fromUtf8(native_impl()); +} +#else GHC_INLINE std::string path::u8string() const { return native_impl(); } +#endif GHC_INLINE std::u16string path::u16string() const { @@ -2437,10 +2474,17 @@ GHC_INLINE std::wstring path::generic_wstring() const return detail::fromUtf8(_path); } +#ifdef __cpp_lib_char8_t +GHC_INLINE std::u8string path::generic_u8string() const +{ + return detail::fromUtf8(_path); +} +#else GHC_INLINE std::string path::generic_u8string() const { return _path; } +#endif GHC_INLINE std::u16string path::generic_u16string() const { @@ -2999,7 +3043,11 @@ GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const , _p1(p1) { if (!_p1.empty()) { +#ifdef __cpp_lib_char8_t + _what_arg += ": '" + detail::toUtf8(_p1.u8string()) + "'"; +#else _what_arg += ": '" + _p1.u8string() + "'"; +#endif } } @@ -3011,10 +3059,18 @@ GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const , _p2(p2) { if (!_p1.empty()) { +#ifdef __cpp_lib_char8_t + _what_arg += ": '" + detail::toUtf8(_p1.u8string()) + "'"; +#else _what_arg += ": '" + _p1.u8string() + "'"; +#endif } if (!_p2.empty()) { +#ifdef __cpp_lib_char8_t + _what_arg += ", '" + detail::toUtf8(_p2.u8string()) + "'"; +#else _what_arg += ", '" + _p2.u8string() + "'"; +#endif } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f8b5ca2..911d1b5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ else() target_compile_definitions(filesystem_test PRIVATE _CRT_SECURE_NO_WARNINGS) endif() ParseAndAddCatchTests(filesystem_test) + AddExecutableWithCppStd(filesystem_test_20 20 filesystem_test.cpp catch.hpp) AddExecutableWithStdFS(std_filesystem_test filesystem_test.cpp catch.hpp) if(WIN32) add_executable(filesystem_test_wchar filesystem_test.cpp catch.hpp) diff --git a/test/filesystem_test.cpp b/test/filesystem_test.cpp index 6681a0a..47db721 100644 --- a/test/filesystem_test.cpp +++ b/test/filesystem_test.cpp @@ -29,8 +29,9 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // //--------------------------------------------------------------------------------------- -#include + #include +#include #include #include #include @@ -48,6 +49,7 @@ #ifdef USE_STD_FS #include +#include namespace fs { using namespace std::filesystem; using ifstream = std::ifstream; @@ -89,6 +91,7 @@ using fstream = ghc::filesystem::fstream; #ifndef GHC_FILESYSTEM_FWD_TEST #define CATCH_CONFIG_MAIN #endif +#define CATCH_CONFIG_NO_WCHAR #include "catch.hpp" //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -135,6 +138,25 @@ struct StringMaker static std::string convert(fs::perms const& value) { return std::to_string(static_cast(value)); } }; +template <> +struct StringMaker +{ + static std::string convert(std::wstring const& value) + { +#ifdef USE_STD_FS +#ifdef GHC_OS_WINDOWS + std::wstring_convert> wcu8; +#else + std::wstring_convert> wcu8; +#endif + return wcu8.to_bytes( value ); +#else + return ghc::filesystem::detail::toUtf8(value); +#endif + } +}; + + template <> struct StringMaker { @@ -269,6 +291,27 @@ static bool has_host_root_name_support() return fs::path("//host").has_root_name(); } +#ifdef __cpp_char8_t +inline std::string U8S(const std::u8string& str) +{ + std::string result(reinterpret_cast(str.data()), str.length()); + return result; +} +inline const char* U8C(const char8_t* str) +{ + return reinterpret_cast(str); +} +#else +inline std::string U8S(const std::string& str) +{ + return str; +} +inline const char* U8C(const char* str) +{ + return str; +} +#endif + template class TestAllocator { @@ -315,13 +358,13 @@ TEST_CASE("fs::detail::fromUtf8", "[filesystem][fs.detail.utf8]") { CHECK(fs::detail::fromUtf8("foobar").length() == 6); CHECK(fs::detail::fromUtf8("foobar") == L"foobar"); - CHECK(fs::detail::fromUtf8(u8"föobar").length() == 6); - CHECK(fs::detail::fromUtf8(u8"föobar") == L"föobar"); + CHECK(fs::detail::fromUtf8(U8S(u8"föobar")).length() == 6); + CHECK(fs::detail::fromUtf8(U8S(u8"föobar")) == L"föobar"); CHECK(fs::detail::toUtf8(std::wstring(L"foobar")).length() == 6); CHECK(fs::detail::toUtf8(std::wstring(L"foobar")) == "foobar"); CHECK(fs::detail::toUtf8(std::wstring(L"föobar")).length() == 7); - CHECK(fs::detail::toUtf8(std::wstring(L"föobar")) == u8"föobar"); + CHECK(fs::detail::toUtf8(std::wstring(L"föobar")) == U8C(u8"föobar")); #ifdef GHC_RAISE_UNICODE_ERRORS CHECK_THROWS_AS(fs::detail::fromUtf8(std::string("\xed\xa0\x80")), fs::filesystem_error); @@ -563,16 +606,21 @@ TEST_CASE("30.10.8.4.6 path native format observers", "[filesystem][path][fs.pat CHECK(fs::u8path("\xc3\xa4\\\xe2\x82\xac").u16string() == std::u16string(u"\u00E4\\\u20AC")); CHECK(fs::u8path("\xc3\xa4\\\xe2\x82\xac").u32string() == std::u32string(U"\U000000E4\\\U000020AC")); #else - CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").native() == fs::path::string_type(u8"\xc3\xa4/\xe2\x82\xac")); - CHECK(!::strcmp(fs::u8path("\xc3\xa4/\xe2\x82\xac").c_str(), u8"\xc3\xa4/\xe2\x82\xac")); - CHECK((std::string)fs::u8path("\xc3\xa4/\xe2\x82\xac") == std::string(u8"\xc3\xa4/\xe2\x82\xac")); - CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").string() == std::string(u8"\xc3\xa4/\xe2\x82\xac")); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").native() == fs::path::string_type(U8S(u8"\xc3\xa4/\xe2\x82\xac"))); + CHECK(!::strcmp(fs::u8path("\xc3\xa4/\xe2\x82\xac").c_str(), U8C(u8"\xc3\xa4/\xe2\x82\xac"))); + CHECK((std::string)fs::u8path("\xc3\xa4/\xe2\x82\xac") == std::string(U8C(u8"\xc3\xa4/\xe2\x82\xac"))); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").string() == std::string(U8C(u8"\xc3\xa4/\xe2\x82\xac"))); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").wstring() == std::wstring(L"\u00E4/\u20AC")); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").wstring() == std::wstring(L"ä/€")); - CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").u8string() == std::string(u8"\xc3\xa4/\xe2\x82\xac")); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").wstring().length() == std::wstring(L"\u00E4/\u20AC").length()); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").u8string() == (u8"\xc3\xa4/\xe2\x82\xac")); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").u16string() == std::u16string(u"\u00E4/\u20AC")); INFO("This check might fail on GCC8 (with \"Illegal byte sequence\") due to not detecting the valid unicode codepoint U+1D11E."); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac\xf0\x9d\x84\x9e").u16string() == std::u16string(u"\u00E4/\u20AC\U0001D11E")); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").u32string() == std::u32string(U"\U000000E4/\U000020AC")); +#ifdef __cpp_char8_t + CHECK(fs::path(u8"\xc3\xa4/\xe2\x82\xac").u8string() == u8"\xc3\xa4/\xe2\x82\xac"); +#endif #endif } @@ -591,13 +639,13 @@ TEST_CASE("30.10.8.4.7 path generic format observers", "[filesystem][path][fs.pa CHECK(fs::u8path("\xc3\xa4\\\xe2\x82\xac").generic_u16string() == std::u16string(u"\u00E4/\u20AC")); CHECK(fs::u8path("\xc3\xa4\\\xe2\x82\xac").generic_u32string() == std::u32string(U"\U000000E4/\U000020AC")); #else - CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_string() == std::string(u8"\xc3\xa4/\xe2\x82\xac")); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_string() == std::string(U8S(u8"\xc3\xa4/\xe2\x82\xac"))); #ifndef USE_STD_FS auto t = fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_string, TestAllocator>(); - CHECK(t.c_str() == std::string(u8"\xc3\xa4/\xe2\x82\xac")); + CHECK(t.c_str() == std::string(U8S(u8"\xc3\xa4/\xe2\x82\xac"))); #endif CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_wstring() == std::wstring(L"ä/€")); - CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_u8string() == std::string(u8"\xc3\xa4/\xe2\x82\xac")); + CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_u8string() == (u8"\xc3\xa4/\xe2\x82\xac")); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_u16string() == std::u16string(u"\u00E4/\u20AC")); CHECK(fs::u8path("\xc3\xa4/\xe2\x82\xac").generic_u32string() == std::u32string(U"\U000000E4/\U000020AC")); #endif diff --git a/test/impl_test.cpp b/test/impl_test.cpp index 1652178..e49cb44 100644 --- a/test/impl_test.cpp +++ b/test/impl_test.cpp @@ -4,6 +4,7 @@ // fs_fwd.hpp (to test this with maximum functionality, the unit tests // are included here, signaling they should only include the fs_fwd.hpp) #define NOMINMAX +#include #include #define CATCH_CONFIG_MAIN #include "catch.hpp"