Prevent templated path members from accepting args convertible to path.

This forces the non-templated overloads accepting path to be chosen instead
of the templated members that expect arguments converible to Source.

This resolves overload resolution ambiguities, when the argument of a
user-defined type is convertible to path and multiple other types that qualify
as Source. By preferring the conversion to path we avoid testing other
conversion paths that may be ambiguous.

Fixes https://github.com/boostorg/filesystem/issues/326.
This commit is contained in:
Andrey Semashev 2024-09-30 02:16:55 +03:00
parent 5746b3f0d6
commit fbd23ee0e0
3 changed files with 100 additions and 26 deletions

View File

@ -45,6 +45,7 @@
<li>As was announced in 1.84.0, Windows versions prior to 10 are no longer supported.</li> <li>As was announced in 1.84.0, Windows versions prior to 10 are no longer supported.</li>
<li>On Windows, <code>canonical</code> is now based on the <code>GetFinalPathNameByHandleW</code> WinAPI function. As a side effect, drive letters are converted to upper case, which makes the resulting paths more interoperable. (<a href="https://github.com/boostorg/filesystem/issues/325">#325</a>)</li> <li>On Windows, <code>canonical</code> is now based on the <code>GetFinalPathNameByHandleW</code> WinAPI function. As a side effect, drive letters are converted to upper case, which makes the resulting paths more interoperable. (<a href="https://github.com/boostorg/filesystem/issues/325">#325</a>)</li>
<li><b>v4:</b> <code>canonical</code> no longer produces a trailing directory separator in the resulting path, if the input path has one.</li> <li><b>v4:</b> <code>canonical</code> no longer produces a trailing directory separator in the resulting path, if the input path has one.</li>
<li>If a <code>path</code> constructor or member function is called with an argument of a user-defined type that is convertible to <code>path</code> and one or more <a href="reference.html#Source"><code>Source</code></a> types, the conversion to <code>path</code> is now chosen by default. This may resolve argument conversion ambiguities in some cases, but may also result in a less optimal conversion path. If a different conversion path is desired, users are recommended to use explicit type casts. (<a href="https://github.com/boostorg/filesystem/issues/326">#326</a>)</li>
</ul> </ul>
<h2>1.86.0</h2> <h2>1.86.0</h2>

View File

@ -25,6 +25,7 @@
#include <boost/assert.hpp> #include <boost/assert.hpp>
#include <boost/system/error_category.hpp> #include <boost/system/error_category.hpp>
#include <boost/iterator/is_iterator.hpp> #include <boost/iterator/is_iterator.hpp>
#include <boost/filesystem/detail/type_traits/negation.hpp>
#include <boost/filesystem/detail/type_traits/conjunction.hpp> #include <boost/filesystem/detail/type_traits/conjunction.hpp>
#if defined(BOOST_FILESYSTEM_DETAIL_CXX23_STRING_VIEW_HAS_IMPLICIT_RANGE_CTOR) #if defined(BOOST_FILESYSTEM_DETAIL_CXX23_STRING_VIEW_HAS_IMPLICIT_RANGE_CTOR)
#include <boost/filesystem/detail/type_traits/disjunction.hpp> #include <boost/filesystem/detail/type_traits/disjunction.hpp>
@ -48,6 +49,7 @@ namespace filesystem {
BOOST_FILESYSTEM_DECL system::error_category const& codecvt_error_category() noexcept; BOOST_FILESYSTEM_DECL system::error_category const& codecvt_error_category() noexcept;
class path;
class directory_entry; class directory_entry;
namespace detail { namespace detail {
@ -501,12 +503,30 @@ no_type check_convertible(...);
} // namespace is_convertible_to_path_source_impl } // namespace is_convertible_to_path_source_impl
//! The type trait indicates whether the type has a conversion path to one of the path source types template< typename T >
struct check_is_convertible_to_path_source :
public std::integral_constant<
bool,
sizeof(is_convertible_to_path_source_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
>
{
};
/*!
* \brief The type trait indicates whether the type has a conversion path to one of the path source types.
*
* \note The type trait returns `false` if the type is convertible to `path`. This prevents testing other
* conversion paths and forces the conversion to `path` to be chosen instead, to invoke a non-template
* member of `path` accepting a `path` argument.
*/
template< typename T > template< typename T >
struct is_convertible_to_path_source : struct is_convertible_to_path_source :
public std::integral_constant< public std::integral_constant<
bool, bool,
sizeof(is_convertible_to_path_source_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type) detail::conjunction<
detail::negation< std::is_convertible< T, path > >,
check_is_convertible_to_path_source< T >
>::value
> >
{ {
}; };
@ -529,7 +549,7 @@ no_type check_convertible(...);
} // namespace is_convertible_to_std_string_view_impl } // namespace is_convertible_to_std_string_view_impl
template< typename T > template< typename T >
struct is_convertible_to_std_string_view : struct check_is_convertible_to_std_string_view :
public std::integral_constant< public std::integral_constant<
bool, bool,
sizeof(is_convertible_to_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type) sizeof(is_convertible_to_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
@ -553,7 +573,7 @@ no_type check_convertible(...);
} // namespace is_convertible_to_path_source_non_std_string_view_impl } // namespace is_convertible_to_path_source_non_std_string_view_impl
template< typename T > template< typename T >
struct is_convertible_to_path_source_non_std_string_view : struct check_is_convertible_to_path_source_non_std_string_view :
public std::integral_constant< public std::integral_constant<
bool, bool,
sizeof(is_convertible_to_path_source_non_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type) sizeof(is_convertible_to_path_source_non_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
@ -561,14 +581,23 @@ struct is_convertible_to_path_source_non_std_string_view :
{ {
}; };
//! The type trait indicates whether the type has a conversion path to one of the path source types /*!
* \brief The type trait indicates whether the type has a conversion path to one of the path source types.
*
* \note The type trait returns `false` if the type is convertible to `path`. This prevents testing other
* conversion paths and forces the conversion to `path` to be chosen instead, to invoke a non-template
* member of `path` accepting a `path` argument.
*/
template< typename T > template< typename T >
struct is_convertible_to_path_source : struct is_convertible_to_path_source :
public std::integral_constant< public std::integral_constant<
bool, bool,
detail::disjunction< detail::conjunction<
is_convertible_to_std_string_view< T >, detail::negation< std::is_convertible< T, path > >,
is_convertible_to_path_source_non_std_string_view< T > detail::disjunction<
check_is_convertible_to_std_string_view< T >,
check_is_convertible_to_path_source_non_std_string_view< T >
>
>::value >::value
> >
{ {
@ -704,7 +733,7 @@ BOOST_FORCEINLINE typename Callback::result_type dispatch_convertible_sv_impl(st
template< typename Source, typename Callback > template< typename Source, typename Callback >
BOOST_FORCEINLINE typename std::enable_if< BOOST_FORCEINLINE typename std::enable_if<
!is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value, !check_is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
typename Callback::result_type typename Callback::result_type
>::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr) >::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr)
{ {
@ -714,7 +743,7 @@ BOOST_FORCEINLINE typename std::enable_if<
template< typename Source, typename Callback > template< typename Source, typename Callback >
BOOST_FORCEINLINE typename std::enable_if< BOOST_FORCEINLINE typename std::enable_if<
is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value, check_is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
typename Callback::result_type typename Callback::result_type
>::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr) >::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr)
{ {

View File

@ -136,6 +136,43 @@ public:
operator fs::path() const { return m_path; } operator fs::path() const { return m_path; }
}; };
//! Test type to verify that the conversion to path is preferred (https://github.com/boostorg/filesystem/issues/326)
class convertible_to_path_and_strings
{
private:
fs::path m_path;
public:
convertible_to_path_and_strings() {}
convertible_to_path_and_strings(convertible_to_path_and_strings const& that) : m_path(that.m_path) {}
template< typename T >
convertible_to_path_and_strings(T const& that) : m_path(that) {}
convertible_to_path_and_strings& operator= (convertible_to_path_and_strings const& that)
{
m_path = that.m_path;
return *this;
}
template< typename T >
convertible_to_path_and_strings& operator= (T const& that)
{
m_path = that;
return *this;
}
operator fs::path() const { return m_path; }
operator const fs::path::value_type*() const
{
#if defined(BOOST_WINDOWS_API)
return L"[invalid path]";
#else
return "[invalid path]";
#endif
}
operator fs::path::string_type() const { return fs::path::string_type(static_cast< const fs::path::value_type* >(*this)); }
};
template< typename Char > template< typename Char >
class basic_custom_string class basic_custom_string
{ {
@ -2188,6 +2225,7 @@ void construction_tests()
PATH_TEST_EQ(derived_from_path("foo"), "foo"); PATH_TEST_EQ(derived_from_path("foo"), "foo");
PATH_TEST_EQ(convertible_to_path("foo"), "foo"); PATH_TEST_EQ(convertible_to_path("foo"), "foo");
PATH_TEST_EQ(convertible_to_path_and_strings("foo"), "foo");
PATH_TEST_EQ(fs::path(pcustom_string("foo")), "foo"); PATH_TEST_EQ(fs::path(pcustom_string("foo")), "foo");
PATH_TEST_EQ(boost::string_view("foo"), "foo"); PATH_TEST_EQ(boost::string_view("foo"), "foo");
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW) #if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
@ -2204,9 +2242,9 @@ void construction_tests()
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW) #if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
#define APPEND_TEST_STD_STRING_VIEW(appnd, expected)\ #define APPEND_TEST_STD_STRING_VIEW(appnd, expected)\
path p6(p);\ path p7(p);\
p6 /= std::string_view(appnd);\ p7 /= std::string_view(appnd);\
PATH_TEST_EQ(p6, expected); PATH_TEST_EQ(p7, expected);
#else #else
#define APPEND_TEST_STD_STRING_VIEW(appnd, expected) #define APPEND_TEST_STD_STRING_VIEW(appnd, expected)
#endif #endif
@ -2229,15 +2267,18 @@ void construction_tests()
p3 /= convertible_to_path(appnd);\ p3 /= convertible_to_path(appnd);\
PATH_TEST_EQ(p3, expected);\ PATH_TEST_EQ(p3, expected);\
path p4(p);\ path p4(p);\
p4 /= pcustom_string(appnd);\ p4 /= convertible_to_path_and_strings(appnd);\
PATH_TEST_EQ(p4, expected);\ PATH_TEST_EQ(p4, expected);\
path p5(p);\ path p5(p);\
p5 /= boost::string_view(appnd);\ p5 /= pcustom_string(appnd);\
PATH_TEST_EQ(p5, expected);\ PATH_TEST_EQ(p5, expected);\
path p6(p);\
p6 /= boost::string_view(appnd);\
PATH_TEST_EQ(p6, expected);\
APPEND_TEST_STD_STRING_VIEW(appnd, expected)\ APPEND_TEST_STD_STRING_VIEW(appnd, expected)\
path p7(p);\ path p8(p);\
p7.append(s.begin(), s.end());\ p8.append(s.begin(), s.end());\
PATH_TEST_EQ(p7.string(), expected);\ PATH_TEST_EQ(p8.string(), expected);\
} }
void append_tests() void append_tests()
@ -2350,9 +2391,9 @@ void append_tests()
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW) #if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
#define CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\ #define CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\
path p9(p);\ path p10(p);\
p9 += std::string_view(appnd);\ p10 += std::string_view(appnd);\
PATH_TEST_EQ(p9, expected); PATH_TEST_EQ(p10, expected);
#else #else
#define CONCAT_TEST_STD_STRING_VIEW(appnd, expected) #define CONCAT_TEST_STD_STRING_VIEW(appnd, expected)
#endif #endif
@ -2380,15 +2421,18 @@ void append_tests()
p6 += convertible_to_path(appnd);\ p6 += convertible_to_path(appnd);\
PATH_TEST_EQ(p6, expected);\ PATH_TEST_EQ(p6, expected);\
path p7(p);\ path p7(p);\
p7 += pcustom_string(appnd);\ p7 += convertible_to_path_and_strings(appnd);\
PATH_TEST_EQ(p7, expected);\ PATH_TEST_EQ(p7, expected);\
path p8(p);\ path p8(p);\
p8 += boost::string_view(appnd);\ p8 += pcustom_string(appnd);\
PATH_TEST_EQ(p8, expected);\ PATH_TEST_EQ(p8, expected);\
path p9(p);\
p9 += boost::string_view(appnd);\
PATH_TEST_EQ(p9, expected);\
CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\ CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\
path p10(p);\ path p11(p);\
p10.concat(s.begin(), s.end());\ p11.concat(s.begin(), s.end());\
PATH_TEST_EQ(p10.string(), expected);\ PATH_TEST_EQ(p11.string(), expected);\
} }
void concat_tests() void concat_tests()