-
Notifications
You must be signed in to change notification settings - Fork 12.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Sema][CTAD] Allow user defined conversion for copy-list-initialization #94752
Conversation
@llvm/pr-subscribers-clang Author: Gábor Spaits (spaits) ChangesThis PR would solve issue #62925. The following code: #include <map>
int main() {
std::map m1 = {std::pair{"foo", 2}, {"bar", 3}}; // guide #<!-- -->2
std::map m2(m1.begin(), m1.end()); // guide #<!-- -->1
} Is rejected by clang, but accepted by both gcc and msvc: https://godbolt.org/z/6v4fvabb5 . So basically CTAD with copy-list-initialization is rejected. Note that this exact code is also used in a cppreference article: https://en.cppreference.com/w/cpp/container/map/deduction_guides I did some debugging and found out, that the reason why this does not work in clang is that, in the function I checked the C++11 and C++20 standard drafts to see whether suppressing user conversion is the correct thing to do for user conversions. Based on the standard I don't think that it is correct.
So we could use user defined conversions according to the standard.
We should not do narrowing.
We should not use explicit constructors. Also a note: Full diff: https://github.com/llvm/llvm-project/pull/94752.diff 3 Files Affected:
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index f443e327eaf32..4b876db436b48 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -603,7 +603,7 @@ class InitializationKind {
/// Normal context
IC_Normal,
- /// Normal context, but allows explicit conversion functionss
+ /// Normal context, but allows explicit conversion functions
IC_ExplicitConvs,
/// Implicit context (value initialization)
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ed8b226a6b39f..211b6887befa3 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -10892,8 +10892,6 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
// FIXME: The "second phase of [over.match.list] case can also
// theoretically happen here, but it's not clear whether we can
// ever have a parameter of the right type.
- bool SuppressUserConversions = Kind.isCopyInit();
-
if (TD) {
SmallVector<Expr *, 8> TmpInits;
for (Expr *E : Inits)
@@ -10903,12 +10901,12 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
TmpInits.push_back(E);
AddTemplateOverloadCandidate(
TD, FoundDecl, /*ExplicitArgs=*/nullptr, TmpInits, Candidates,
- SuppressUserConversions,
+ /*SuppressUserConversions=*/false,
/*PartialOverloading=*/false, AllowExplicit, ADLCallKind::NotADL,
/*PO=*/{}, AllowAggregateDeductionCandidate);
} else {
AddOverloadCandidate(GD, FoundDecl, Inits, Candidates,
- SuppressUserConversions,
+ /*SuppressUserConversions=*/false,
/*PartialOverloading=*/false, AllowExplicit);
}
};
diff --git a/clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp b/clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp
new file mode 100644
index 0000000000000..21b1137158d5a
--- /dev/null
+++ b/clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp
@@ -0,0 +1,90 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++20 %s
+namespace std
+{
+ typedef long unsigned int size_t;
+}
+
+namespace std
+{
+ template<class _E>
+ class initializer_list
+ {
+ public:
+ typedef _E value_type;
+ typedef const _E& reference;
+ typedef const _E& const_reference;
+ typedef size_t size_type;
+ typedef const _E* iterator;
+ typedef const _E* const_iterator;
+
+ private:
+ iterator _M_array;
+ size_type _M_len;
+
+
+ constexpr initializer_list(const_iterator __a, size_type __l)
+ : _M_array(__a), _M_len(__l) { }
+
+ public:
+ constexpr initializer_list() noexcept
+ : _M_array(0), _M_len(0) { }
+
+
+ constexpr size_type
+ size() const noexcept { return _M_len; }
+
+
+ constexpr const_iterator
+ begin() const noexcept { return _M_array; }
+
+
+ constexpr const_iterator
+ end() const noexcept { return begin() + size(); }
+ };
+
+ template<class _Tp>
+ constexpr const _Tp*
+ begin(initializer_list<_Tp> __ils) noexcept
+ { return __ils.begin(); }
+
+ template<class _Tp>
+ constexpr const _Tp*
+ end(initializer_list<_Tp> __ils) noexcept
+ { return __ils.end(); }
+}
+
+template<class T, class Y>
+class pair{
+ private:
+ T fst;
+ Y snd;
+ public:
+ pair(T f, Y s) : fst(f), snd(s) {}
+};
+
+template<class T, class Y>
+class map {
+ public:
+ map(std::initializer_list<pair<T, Y>>, int a = 4, int b = 5) {}
+};
+
+template<class T, class Y>
+class Contained {
+ public:
+ Contained(T, Y) {}
+};
+
+template<class T, class Y>
+class A {
+ public:
+ A(std::initializer_list<Contained<T, Y> >, int) {}
+};
+
+int main() {
+ map mOk ={pair{5, 'a'}, {6, 'b'}, {7, 'c'}};
+ map mNarrow ={pair{5, 'a'}, {6.0f, 'b'}, {7, 'c'}}; // expected-error {{type 'float' cannot be narrowed to 'int' in initializer list}} // expected-note {{insert an explicit cast to silence this issue}}
+
+ A aOk = {{Contained{5, 'c'}, {5, 'c'}}, 5};
+ A aNarrowNested = {{Contained{5, 'c'}, {5.0f, 'c'}}, 5}; // expected-error {{type 'float' cannot be narrowed to 'int' in initializer list}} // expected-note {{insert an explicit cast to silence this issue}}
+ A aNarrow = {{Contained{5, 'c'}, {5, 'c'}}, 5.0f}; // expected-error {{type 'float' cannot be narrowed to 'int' in initializer list}} // expected-note {{insert an explicit cast to silence this issue}}
+}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this. While I didn't dig into the details in DeduceTemplateSpecializationFromInitializer()
, I think the test could be improved before proceeding. I also wish @erichkeane and @hokein could take a look at this because they have more expertise in CTAD. :)
}; | ||
|
||
int main() { | ||
map mOk ={pair{5, 'a'}, {6, 'b'}, {7, 'c'}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you try to simplify the test case a bit? There is so much boilerplate that it's hard to understand the issue.
@zyn0217 Thank you for checking my PR. I was able to simplify the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change looks good to me. Please add a note in llvm-project/clang/docs/ReleaseNotes.rst
.
@@ -0,0 +1,65 @@ | |||
// RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++20 %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please format the test file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied clang format, nothing has really changed. I have changed the formatting by hand at some places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's expected, we disable the clang-format for all lit test files (see the clang/test/.clang-format
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, looks good.
@@ -0,0 +1,65 @@ | |||
// RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++20 %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's expected, we disable the clang-format for all lit test files (see the clang/test/.clang-format
).
Thank you for reviewing. When the CI run finishes and it is successful I will merge this PR. |
// FIXME: The "second phase of [over.match.list] case can also | ||
// theoretically happen here, but it's not clear whether we can | ||
// ever have a parameter of the right type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that comment still useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wasn't useful (or at least I could definitely not understand why it was there) even when I was debugging to find the solution. Since I did not understand it I did not touch it.
What do you think, could we remove it?
- User defined constructors are allowed for copy-list-initialization with CTAD. | ||
The example code for deduction guides for std::map in | ||
(`cppreference <https://en.cppreference.com/w/cpp/container/map/deduction_guides>`_) | ||
will now work. | ||
(#GH62925). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure the link adds useful information. If you think the example is useful, feel free to add the examples in the release note directly (but the issue link is enough)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will change it so only the issue link is there.
…on (llvm#94752) Fixes llvm#62925. The following code: ```cpp #include <map> int main() { std::map m1 = {std::pair{"foo", 2}, {"bar", 3}}; // guide llvm#2 std::map m2(m1.begin(), m1.end()); // guide llvm#1 } ``` Is rejected by clang, but accepted by both gcc and msvc: https://godbolt.org/z/6v4fvabb5 . So basically CTAD with copy-list-initialization is rejected. Note that this exact code is also used in a cppreference article: https://en.cppreference.com/w/cpp/container/map/deduction_guides I checked the C++11 and C++20 standard drafts to see whether suppressing user conversion is the correct thing to do for user conversions. Based on the standard I don't think that it is correct. ``` 13.3.1.4 Copy-initialization of class by user-defined conversion [over.match.copy] Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized. Overload resolution is used to select the user-defined conversion to be invoked ``` So we could use user defined conversions according to the standard. ``` If a narrowing conversion is required to initialize any of the elements, the program is ill-formed. ``` We should not do narrowing. ``` In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. ``` We should not use explicit constructors.
Fixes #62925.
The following code:
Is rejected by clang, but accepted by both gcc and msvc: https://godbolt.org/z/6v4fvabb5 .
So basically CTAD with copy-list-initialization is rejected.
Note that this exact code is also used in a cppreference article: https://en.cppreference.com/w/cpp/container/map/deduction_guides
I did some debugging and found out, that the reason why this does not work in clang is that, in the function
Sema::DeduceTemplateSpecializationFromInitializer
we explicitly suppress user conversion, if the initialization type is a copy initialization.I checked the C++11 and C++20 standard drafts to see whether suppressing user conversion is the correct thing to do for user conversions. Based on the standard I don't think that it is correct.
So we could use user defined conversions according to the standard.
We should not do narrowing.
We should not use explicit constructors.
Also a note:
In
InitializationKind::InitKind
there is no init kind calledIK_CopyList
but there isIK_DirectList
, should we addIK_CopyList
? Or it is unnecessary for now?