basic_string
CTAD ambiguitySection: 27.4.3.3 [string.cons] Status: C++20 Submitter: Stephan T. Lavavej Opened: 2018-03-03 Last modified: 2021-02-25
Priority: Not Prioritized
View all other issues in [string.cons].
View all issues with C++20 status.
Discussion:
The following code fails to compile for surprising reasons.
#include <string> #include <string_view> using namespace std; int main() { string s0; basic_string s1(s0, 1, 1); // WANT: basic_string(const basic_string&, size_type, size_type, const Allocator& = Allocator()) // CONFLICT: basic_string(size_type, charT, const Allocator&) basic_string s2("cat"sv, 1, 1); // WANT: basic_string(const T&, size_type, size_type, const Allocator& = Allocator()) // CONFLICT: basic_string(size_type, charT, const Allocator&) basic_string s3("cat", 1); // WANT: basic_string(const charT *, size_type, const Allocator& = Allocator()) // CONFLICT: basic_string(const charT *, const Allocator&) }
For s1
and s2
, the signature basic_string(size_type, charT, const Allocator&)
participates in CTAD. size_type
is non-deduced (it will be substituted later, so the compiler
can't immediately realize that s0
or "cat"sv
are totally non-viable arguments).
charT
is deduced to be int
(weird, but not the problem). Finally, Allocator
is deduced to be int
. Then the compiler tries to substitute for size_type
, but
this ends up giving int to allocator_traits
in a non-SFINAE context, so compilation fails.
s3
fails for a slightly different reason. basic_string(const charT *, const Allocator&)
participates in CTAD, deducing charT
to be char
(good) and Allocator
to be
int
. This is an exact match, which is better than the constructor that the user actually wants
(where int
would need to be converted to size_type
, which is unsigned). So CTAD deduces basic_string<char, char_traits<char>, int>
, which is the wrong type.
This problem appears to be unique to basic_string
and its heavily overloaded set of constructors.
I haven't figured out how to fix it by adding (non-greedy) deduction guides. The conflicting constructors
are always considered during CTAD, regardless of whether deduction guides are provided that correspond
to the desired or conflicting constructors. (That's because deduction guides are preferred as a late
tiebreaker in overload resolution; if a constructor provides a better match it will be chosen before
tiebreaking.) It appears that we need to constrain the conflicting constructors themselves; this will
have no effect on actual usage (where Allocator
will be an allocator) but will prevent CTAD
from considering them for non-allocators. As this is unusual, I believe it deserves a Note.
This has been implemented in MSVC.
[2018-3-14 Wednesday evening issues processing; move to Ready]
[2018-06 Rapperswil: Adopted]
Proposed resolution:
This wording is relative to N4727.
Edit 27.4.3.3 [string.cons] as indicated:
basic_string(const charT* s, const Allocator& a = Allocator());-14- Requires:
-15- Effects: Constructs an object of classs
points to an array of at leasttraits::length(s) + 1
elements ofcharT
.basic_string
and determines its initial string value from the array ofcharT
of lengthtraits::length(s)
whose first element is designated bys
. -16- Postconditions:data()
points at the first element of an allocated copy of the array whose first element is pointed at bys
,size()
is equal totraits::length(s)
, andcapacity()
is a value at least as large assize()
. -?- Remarks: Shall not participate in overload resolution ifAllocator
is a type that does not qualify as an allocator (23.2.2 [container.requirements.general]). [Note: This affects class template argument deduction. — end note]basic_string(size_type n, charT c, const Allocator& a = Allocator());-17- Requires:
-18- Effects: Constructs an object of classn < npos
.basic_string
and determines its initial string value by repeating the char-like objectc
for alln
elements. -19- Postconditions:data()
points at the first element of an allocated array ofn
elements, each storing the initial valuec
,size()
is equal ton
, andcapacity()
is a value at least as large assize()
. -?- Remarks: Shall not participate in overload resolution ifAllocator
is a type that does not qualify as an allocator (23.2.2 [container.requirements.general]). [Note: This affects class template argument deduction. — end note]