Skip to content

Commit fbd23ee

Browse files
committed
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 #326.
1 parent 5746b3f commit fbd23ee

File tree

3 files changed

+100
-26
lines changed

3 files changed

+100
-26
lines changed

doc/release_history.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ <h2>1.87.0</h2>
4545
<li>As was announced in 1.84.0, Windows versions prior to 10 are no longer supported.</li>
4646
<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>
4747
<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>
48+
<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>
4849
</ul>
4950

5051
<h2>1.86.0</h2>

include/boost/filesystem/detail/path_traits.hpp

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <boost/assert.hpp>
2626
#include <boost/system/error_category.hpp>
2727
#include <boost/iterator/is_iterator.hpp>
28+
#include <boost/filesystem/detail/type_traits/negation.hpp>
2829
#include <boost/filesystem/detail/type_traits/conjunction.hpp>
2930
#if defined(BOOST_FILESYSTEM_DETAIL_CXX23_STRING_VIEW_HAS_IMPLICIT_RANGE_CTOR)
3031
#include <boost/filesystem/detail/type_traits/disjunction.hpp>
@@ -48,6 +49,7 @@ namespace filesystem {
4849

4950
BOOST_FILESYSTEM_DECL system::error_category const& codecvt_error_category() noexcept;
5051

52+
class path;
5153
class directory_entry;
5254

5355
namespace detail {
@@ -501,16 +503,34 @@ no_type check_convertible(...);
501503

502504
} // namespace is_convertible_to_path_source_impl
503505

504-
//! The type trait indicates whether the type has a conversion path to one of the path source types
505506
template< typename T >
506-
struct is_convertible_to_path_source :
507+
struct check_is_convertible_to_path_source :
507508
public std::integral_constant<
508509
bool,
509510
sizeof(is_convertible_to_path_source_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
510511
>
511512
{
512513
};
513514

515+
/*!
516+
* \brief The type trait indicates whether the type has a conversion path to one of the path source types.
517+
*
518+
* \note The type trait returns `false` if the type is convertible to `path`. This prevents testing other
519+
* conversion paths and forces the conversion to `path` to be chosen instead, to invoke a non-template
520+
* member of `path` accepting a `path` argument.
521+
*/
522+
template< typename T >
523+
struct is_convertible_to_path_source :
524+
public std::integral_constant<
525+
bool,
526+
detail::conjunction<
527+
detail::negation< std::is_convertible< T, path > >,
528+
check_is_convertible_to_path_source< T >
529+
>::value
530+
>
531+
{
532+
};
533+
514534
#else // !defined(BOOST_FILESYSTEM_DETAIL_CXX23_STRING_VIEW_HAS_IMPLICIT_RANGE_CTOR)
515535

516536
// Note: We use separate checks for convertibility to std::string_view and other types to avoid ambiguity with an implicit range constructor
@@ -529,7 +549,7 @@ no_type check_convertible(...);
529549
} // namespace is_convertible_to_std_string_view_impl
530550

531551
template< typename T >
532-
struct is_convertible_to_std_string_view :
552+
struct check_is_convertible_to_std_string_view :
533553
public std::integral_constant<
534554
bool,
535555
sizeof(is_convertible_to_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
@@ -553,22 +573,31 @@ no_type check_convertible(...);
553573
} // namespace is_convertible_to_path_source_non_std_string_view_impl
554574

555575
template< typename T >
556-
struct is_convertible_to_path_source_non_std_string_view :
576+
struct check_is_convertible_to_path_source_non_std_string_view :
557577
public std::integral_constant<
558578
bool,
559579
sizeof(is_convertible_to_path_source_non_std_string_view_impl::check_convertible(std::declval< T const& >())) == sizeof(yes_type)
560580
>
561581
{
562582
};
563583

564-
//! The type trait indicates whether the type has a conversion path to one of the path source types
584+
/*!
585+
* \brief The type trait indicates whether the type has a conversion path to one of the path source types.
586+
*
587+
* \note The type trait returns `false` if the type is convertible to `path`. This prevents testing other
588+
* conversion paths and forces the conversion to `path` to be chosen instead, to invoke a non-template
589+
* member of `path` accepting a `path` argument.
590+
*/
565591
template< typename T >
566592
struct is_convertible_to_path_source :
567593
public std::integral_constant<
568594
bool,
569-
detail::disjunction<
570-
is_convertible_to_std_string_view< T >,
571-
is_convertible_to_path_source_non_std_string_view< T >
595+
detail::conjunction<
596+
detail::negation< std::is_convertible< T, path > >,
597+
detail::disjunction<
598+
check_is_convertible_to_std_string_view< T >,
599+
check_is_convertible_to_path_source_non_std_string_view< T >
600+
>
572601
>::value
573602
>
574603
{
@@ -704,7 +733,7 @@ BOOST_FORCEINLINE typename Callback::result_type dispatch_convertible_sv_impl(st
704733

705734
template< typename Source, typename Callback >
706735
BOOST_FORCEINLINE typename std::enable_if<
707-
!is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
736+
!check_is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
708737
typename Callback::result_type
709738
>::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr)
710739
{
@@ -714,7 +743,7 @@ BOOST_FORCEINLINE typename std::enable_if<
714743

715744
template< typename Source, typename Callback >
716745
BOOST_FORCEINLINE typename std::enable_if<
717-
is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
746+
check_is_convertible_to_std_string_view< typename std::remove_cv< Source >::type >::value,
718747
typename Callback::result_type
719748
>::type dispatch_convertible(Source const& source, Callback cb, const codecvt_type* cvt = nullptr)
720749
{

test/path_test.cpp

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,43 @@ class convertible_to_path
136136
operator fs::path() const { return m_path; }
137137
};
138138

139+
//! Test type to verify that the conversion to path is preferred (https://github.com/boostorg/filesystem/issues/326)
140+
class convertible_to_path_and_strings
141+
{
142+
private:
143+
fs::path m_path;
144+
145+
public:
146+
convertible_to_path_and_strings() {}
147+
convertible_to_path_and_strings(convertible_to_path_and_strings const& that) : m_path(that.m_path) {}
148+
template< typename T >
149+
convertible_to_path_and_strings(T const& that) : m_path(that) {}
150+
151+
convertible_to_path_and_strings& operator= (convertible_to_path_and_strings const& that)
152+
{
153+
m_path = that.m_path;
154+
return *this;
155+
}
156+
template< typename T >
157+
convertible_to_path_and_strings& operator= (T const& that)
158+
{
159+
m_path = that;
160+
return *this;
161+
}
162+
163+
operator fs::path() const { return m_path; }
164+
operator const fs::path::value_type*() const
165+
{
166+
#if defined(BOOST_WINDOWS_API)
167+
return L"[invalid path]";
168+
#else
169+
return "[invalid path]";
170+
#endif
171+
}
172+
operator fs::path::string_type() const { return fs::path::string_type(static_cast< const fs::path::value_type* >(*this)); }
173+
};
174+
175+
139176
template< typename Char >
140177
class basic_custom_string
141178
{
@@ -2188,6 +2225,7 @@ void construction_tests()
21882225

21892226
PATH_TEST_EQ(derived_from_path("foo"), "foo");
21902227
PATH_TEST_EQ(convertible_to_path("foo"), "foo");
2228+
PATH_TEST_EQ(convertible_to_path_and_strings("foo"), "foo");
21912229
PATH_TEST_EQ(fs::path(pcustom_string("foo")), "foo");
21922230
PATH_TEST_EQ(boost::string_view("foo"), "foo");
21932231
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
@@ -2204,9 +2242,9 @@ void construction_tests()
22042242

22052243
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
22062244
#define APPEND_TEST_STD_STRING_VIEW(appnd, expected)\
2207-
path p6(p);\
2208-
p6 /= std::string_view(appnd);\
2209-
PATH_TEST_EQ(p6, expected);
2245+
path p7(p);\
2246+
p7 /= std::string_view(appnd);\
2247+
PATH_TEST_EQ(p7, expected);
22102248
#else
22112249
#define APPEND_TEST_STD_STRING_VIEW(appnd, expected)
22122250
#endif
@@ -2229,15 +2267,18 @@ void construction_tests()
22292267
p3 /= convertible_to_path(appnd);\
22302268
PATH_TEST_EQ(p3, expected);\
22312269
path p4(p);\
2232-
p4 /= pcustom_string(appnd);\
2270+
p4 /= convertible_to_path_and_strings(appnd);\
22332271
PATH_TEST_EQ(p4, expected);\
22342272
path p5(p);\
2235-
p5 /= boost::string_view(appnd);\
2273+
p5 /= pcustom_string(appnd);\
22362274
PATH_TEST_EQ(p5, expected);\
2275+
path p6(p);\
2276+
p6 /= boost::string_view(appnd);\
2277+
PATH_TEST_EQ(p6, expected);\
22372278
APPEND_TEST_STD_STRING_VIEW(appnd, expected)\
2238-
path p7(p);\
2239-
p7.append(s.begin(), s.end());\
2240-
PATH_TEST_EQ(p7.string(), expected);\
2279+
path p8(p);\
2280+
p8.append(s.begin(), s.end());\
2281+
PATH_TEST_EQ(p8.string(), expected);\
22412282
}
22422283

22432284
void append_tests()
@@ -2350,9 +2391,9 @@ void append_tests()
23502391

23512392
#if !defined(BOOST_NO_CXX17_HDR_STRING_VIEW)
23522393
#define CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\
2353-
path p9(p);\
2354-
p9 += std::string_view(appnd);\
2355-
PATH_TEST_EQ(p9, expected);
2394+
path p10(p);\
2395+
p10 += std::string_view(appnd);\
2396+
PATH_TEST_EQ(p10, expected);
23562397
#else
23572398
#define CONCAT_TEST_STD_STRING_VIEW(appnd, expected)
23582399
#endif
@@ -2380,15 +2421,18 @@ void append_tests()
23802421
p6 += convertible_to_path(appnd);\
23812422
PATH_TEST_EQ(p6, expected);\
23822423
path p7(p);\
2383-
p7 += pcustom_string(appnd);\
2424+
p7 += convertible_to_path_and_strings(appnd);\
23842425
PATH_TEST_EQ(p7, expected);\
23852426
path p8(p);\
2386-
p8 += boost::string_view(appnd);\
2427+
p8 += pcustom_string(appnd);\
23872428
PATH_TEST_EQ(p8, expected);\
2429+
path p9(p);\
2430+
p9 += boost::string_view(appnd);\
2431+
PATH_TEST_EQ(p9, expected);\
23882432
CONCAT_TEST_STD_STRING_VIEW(appnd, expected)\
2389-
path p10(p);\
2390-
p10.concat(s.begin(), s.end());\
2391-
PATH_TEST_EQ(p10.string(), expected);\
2433+
path p11(p);\
2434+
p11.concat(s.begin(), s.end());\
2435+
PATH_TEST_EQ(p11.string(), expected);\
23922436
}
23932437

23942438
void concat_tests()

0 commit comments

Comments
 (0)