[Bug c++/114076] list-initialization with assignment expression triggers wrong template instanciation
benni.buch at gmail dot com
gcc-bugzilla@gcc.gnu.org
Tue Mar 5 12:26:04 GMT 2024
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114076
--- Comment #3 from Benjamin Buch <benni.buch at gmail dot com> ---
I [created an overview](https://stackoverflow.com/a/78101462/4821621) with all
cases that currently work on StackOverflow. I think that all these cases should
be valid. For a properly formated version with links to Compiler Explorer
please use my stackoverflow post. Here is the relevant part
GitHub user
[frederick-vs-ja](https://github.com/microsoft/STL/issues/4417#issuecomment-1960976417)
has reduced the problem in the MS STL Bug Report to a minimal example.
```cpp
template <typename T>
struct Holder {
Holder() = default;
constexpr ~Holder() { static_assert(sizeof(T) || true); }
};
struct Incomplete;
struct Class {
Class();
~Class();
Holder<Incomplete> v{};
};
int main() { [[maybe_unused]] Class v; }
```
I have extended this example with some variants and tested the compilers with
them. Syntactically, there are 7 ways to call the default constructor of
Holder:
- `Holder<Incomplete> a;` default-initialization
- `Holder<Incomplete> b{};` direct-list-initialization
- `Holder<Incomplete> c = {};` copy-list-initialization
- `Holder<Incomplete> d = Holder<Incomplete>();` copy-direct-initialization
- `Holder<Incomplete> e = {Holder<Incomplete>()};` copy-list-initialization
- `Holder<Incomplete> f = Holder<Incomplete>{};` copy-direct-initialization
- `Holder<Incomplete> g = {Holder<Incomplete>{}};` copy-list-initialization
Then we can change `Holder` in two ways. (Thanks to Github user
[fsb4000](https://github.com/microsoft/STL/issues/4417#issuecomment-1959604006)
in the MS STL bug report!)
First we can remove the explicit constructor definition, so the compiler
generates this constructor implicitly:
```cpp
template <typename T>
struct Holder {
constexpr ~Holder() { static_assert(sizeof(T) || true); }
};
```
Second we can remove the constexpr from the destructor:
```cpp
template <typename T>
struct Holder {
Holder() = default;
~Holder() { static_assert(sizeof(T) || true); }
};
```
A third relevant change can be applied to `B`. (Thanks to [Jiang
An](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114076#c1) in the GCC bug
report!) We can make it a template, by giving it a default template argument:
```cpp
template <typename = void>
struct Class {
Class();
~Class();
Holder<Incomplete> v{};
};
```
So we have 8 code examples with 7 kinds of initialization. I created a table
with what works with which compiler. I tested with MSVC 19.38, GCC 13.2 and
clang 17.0.1. Each table cell links to the live code, where the trunk versions
of the compilers are used additionally.
- Case 1: *Implicit `Holder` ctor*, *non-`constexpr` dtor*, *non-template
`Class`*
- Case 2: Explicit `Holder` ctor, *non-`constexpr` dtor*, *non-template
`Class`*
- Case 3: *Implicit `Holder` ctor*, `constexpr` dtor, *non-template `Class`*
- Case 4: Explicit `Holder` ctor, `constexpr` dtor, *non-template `Class`*
- Case 5: *Implicit `Holder` ctor*, *non-`constexpr` dtor*, template `Class`
- Case 6: Explicit `Holder` ctor, *non-`constexpr` dtor*, template `Class`
- Case 7: *Implicit `Holder` ctor*, `constexpr` dtor, template `Class`
- Case 8: Explicit `Holder` ctor, `constexpr` dtor, template `Class`
The order is: ✅/❌ MSVC ; ✅/❌ GCC ; ✅/❌ clang
||Case a|Case b|Case c|Case d|Case e|Case f|Case g|
|-|-|-|-|-|-|-|-|
|Case 1:|✅✅✅|✅✅✅|✅✅✅|✅❌✅|✅❌✅|✅❌✅|✅❌✅|
|Case 2:|✅✅✅|✅✅✅|✅❌✅|✅❌✅|✅❌✅|✅❌✅|✅❌✅|
|Case 3:|✅✅✅|❌✅✅|❌✅✅|❌❌❌|❌❌❌|❌❌❌|❌❌❌|
|Case 4:|✅✅✅|❌✅✅|❌❌✅|❌❌❌|❌❌❌|❌❌❌|❌❌❌|
|Case 5:|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|
|Case 6:|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|✅✅✅|
|Case 7:|✅✅✅|✅✅✅|✅✅✅|✅✅❌|✅✅❌|✅✅❌|✅✅❌|
|Case 8:|✅✅✅|✅✅✅|✅✅✅|✅✅❌|✅✅❌|✅✅❌|✅✅❌|
Cases d, e, f and g can apparently be considered as one case.
More information about the Gcc-bugs
mailing list