Skip to content
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

Merged
merged 5 commits into from
Jun 18, 2024

Conversation

spaits
Copy link
Contributor

@spaits spaits commented Jun 7, 2024

Fixes #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 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.

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.

Also a note:
In InitializationKind::InitKind there is no init kind called IK_CopyList but there is IK_DirectList, should we add IK_CopyList? Or it is unnecessary for now?

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 7, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jun 7, 2024

@llvm/pr-subscribers-clang

Author: Gábor Spaits (spaits)

Changes

This PR would solve issue #62925.

The following code:

#include &lt;map&gt;

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 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.

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.

Also a note:
In InitializationKind::InitKind there is no init kind called IK_CopyList but there is IK_DirectList, should we add IK_CopyList? Or it is unnecessary for now?


Full diff: https://github.com/llvm/llvm-project/pull/94752.diff

3 Files Affected:

  • (modified) clang/include/clang/Sema/Initialization.h (+1-1)
  • (modified) clang/lib/Sema/SemaInit.cpp (+2-4)
  • (added) clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp (+90)
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}}
+}

@spaits spaits requested a review from zyn0217 June 7, 2024 19:30
Copy link
Contributor

@zyn0217 zyn0217 left a 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'}};
Copy link
Contributor

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 zyn0217 requested review from hokein and erichkeane June 9, 2024 04:10
@spaits
Copy link
Contributor Author

spaits commented Jun 9, 2024

@zyn0217 Thank you for checking my PR. I was able to simplify the std::initializer_list definition. I have also created two different "categories" for the test. One test contains almost the exact code as the one in the reported issue with the same class names. The other one is there to test whether we can handle conversions correctly on multiple levels of nesting (one level deep and top level, I have added this since in the code in the issue everything interesting happens on the first level).

@spaits
Copy link
Contributor Author

spaits commented Jun 17, 2024

@zyn0217 @cor3ntin Sorry to ping you. Could you please take another look at this PR? Are my assumptions about the standard correct? Are the tests good now or I should make other changes?

Copy link
Collaborator

@hokein hokein left a 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.

clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp Outdated Show resolved Hide resolved
clang/test/SemaCXX/ctad-copy-init-list-narrowing.cpp Outdated Show resolved Hide resolved
@@ -0,0 +1,65 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++20 %s
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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).

Copy link
Collaborator

@hokein hokein left a 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
Copy link
Collaborator

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).

@spaits
Copy link
Contributor Author

spaits commented Jun 18, 2024

thanks, looks good.

Thank you for reviewing. When the CI run finishes and it is successful I will merge this PR.

@spaits spaits merged commit f80bd9b into llvm:main Jun 18, 2024
8 checks passed
Comment on lines 10892 to 10894
// 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.
Copy link
Contributor

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?

Copy link
Contributor Author

@spaits spaits Jun 20, 2024

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?

Comment on lines +202 to +207
- 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).

Copy link
Contributor

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)

Copy link
Contributor Author

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.

spaits added a commit that referenced this pull request Jul 6, 2024
As discussed before with @cor3ntin before
(#94752) here is the
simplification of the release note written for the previously mentioned
PR and the removal of a comment that is no longer useful.

(Sorry for creating this PR this late.)

Co-authored-by: Gabor Spaits <Gabor.Spaits@hightec-rt.com>
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang issue in CTAD
5 participants