Date: Sat, 12 Dec 2020 01:00:48 -0500
On Fri, Dec 11, 2020 at 10:29 PM Tam S. B. via Std-Discussion
<std-discussion_at_[hidden]> wrote:
>
> [dcl.init.general]/8:
>
> > To _value-initialize_ an object of type T means:
> >
> > - if T is a (possibly cv-qualified) class type ([class]), then
> > - if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
> > - otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
> > - if T is an array type, then each element is value-initialized;
> > - otherwise, the object is zero-initialized.
>
> Consider
>
> ```
> template<bool Cond>
> struct A {
> A() = default; // #1
> template<class=void> A() {} // #2
> A() requires Cond = delete; // #3
> int x;
> };
>
> void f() {
> constexpr A<false> a = {};
> static_assert(a.x == 0);
> }
> ```
>
> Here `A` "has" a default constructor that is user-provided (#2) and one that is deleted (#3), although neither will be selected by overload resolution. Therefore, according to [dcl.init.general]/8, the variable `a` is *not* zero-initialized, and thus `a.x` has indeterminate value, which makes the static_assert invalid.
>
> That is, #2 and #3 affect the semantics of the code, even though they are not selected.
>
> This seems counter-intuitive and hard to learn,
So is giving a class multiple default constructors, but you don't seem
to have a problem doing that ;)
While that is rather flippant, it is kind of the point; you've written
code that doesn't really make sense. The "has a default constructor"
rule is *intended* to be for two cases. In one case, the default
constructor you wrote actually does something, so your expectation is
that anytime someone initializes the object without values, that
constructor ought to be called to do that initialization. Hence
default-initialization is performed. In the case of a deleted default
constructor, the idea is that you don't *want* a user to be able to
construct the object without arguments, so default-initialization will
give a proper error message for trying to do it.
Your class `A` is neither of those things. The intent behind line 2 in
the presence of line 1 is unclear. If line 2 is trying to do some
SFINAE thing based on template parameters for `A` if it were a
template, then it's not clear why line 1 exists. Presumably, if you
wanted to `=default` the default case, then you would use the negation
of the SFINAE condition, just as with other uses of SFINAE.
Let's ignore line 2 and focus on line 3, which is the more interesting
case. I believe that the intent here is that, if `Cond` is true, then
you want the type's default constructor to be deleted, but if `Cond`
is false, you want it to be defaulted. OK.
This probably represents a genuine defect. The standard has the
concept of an "eligible special member function", which per
[special]/6, is basically a special member function that is not
deleted, and is the most constrained version of that special member
function whose constraint is satisfied. It would seem to me that the
intent is that, if you try to call the special member function through
the usual mechanisms, the eligible special member will be the one that
gets called following overload resolution.
If that's the case, it seems to me that the definition of
value-initialization needs to take into account eligibility of the
default constructor. Or at the very least, it needs to check with
overload resolution to see which gets called (since deleted functions
are ineligible... for some reason).
>and disagrees with all compilers I tested (GCC/Clang/MSVC), which all accept the code, despite the existence of `#2` and `#3`.
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
<std-discussion_at_[hidden]> wrote:
>
> [dcl.init.general]/8:
>
> > To _value-initialize_ an object of type T means:
> >
> > - if T is a (possibly cv-qualified) class type ([class]), then
> > - if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
> > - otherwise, the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
> > - if T is an array type, then each element is value-initialized;
> > - otherwise, the object is zero-initialized.
>
> Consider
>
> ```
> template<bool Cond>
> struct A {
> A() = default; // #1
> template<class=void> A() {} // #2
> A() requires Cond = delete; // #3
> int x;
> };
>
> void f() {
> constexpr A<false> a = {};
> static_assert(a.x == 0);
> }
> ```
>
> Here `A` "has" a default constructor that is user-provided (#2) and one that is deleted (#3), although neither will be selected by overload resolution. Therefore, according to [dcl.init.general]/8, the variable `a` is *not* zero-initialized, and thus `a.x` has indeterminate value, which makes the static_assert invalid.
>
> That is, #2 and #3 affect the semantics of the code, even though they are not selected.
>
> This seems counter-intuitive and hard to learn,
So is giving a class multiple default constructors, but you don't seem
to have a problem doing that ;)
While that is rather flippant, it is kind of the point; you've written
code that doesn't really make sense. The "has a default constructor"
rule is *intended* to be for two cases. In one case, the default
constructor you wrote actually does something, so your expectation is
that anytime someone initializes the object without values, that
constructor ought to be called to do that initialization. Hence
default-initialization is performed. In the case of a deleted default
constructor, the idea is that you don't *want* a user to be able to
construct the object without arguments, so default-initialization will
give a proper error message for trying to do it.
Your class `A` is neither of those things. The intent behind line 2 in
the presence of line 1 is unclear. If line 2 is trying to do some
SFINAE thing based on template parameters for `A` if it were a
template, then it's not clear why line 1 exists. Presumably, if you
wanted to `=default` the default case, then you would use the negation
of the SFINAE condition, just as with other uses of SFINAE.
Let's ignore line 2 and focus on line 3, which is the more interesting
case. I believe that the intent here is that, if `Cond` is true, then
you want the type's default constructor to be deleted, but if `Cond`
is false, you want it to be defaulted. OK.
This probably represents a genuine defect. The standard has the
concept of an "eligible special member function", which per
[special]/6, is basically a special member function that is not
deleted, and is the most constrained version of that special member
function whose constraint is satisfied. It would seem to me that the
intent is that, if you try to call the special member function through
the usual mechanisms, the eligible special member will be the one that
gets called following overload resolution.
If that's the case, it seems to me that the definition of
value-initialization needs to take into account eligibility of the
default constructor. Or at the very least, it needs to check with
overload resolution to see which gets called (since deleted functions
are ineligible... for some reason).
>and disagrees with all compilers I tested (GCC/Clang/MSVC), which all accept the code, despite the existence of `#2` and `#3`.
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
Received on 2020-12-12 00:01:02