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

CWG1808 [class.prop] Templated default constructors should not be considered for eligibility #185

Open
royjacobson opened this issue Nov 28, 2022 · 6 comments

Comments

@royjacobson
Copy link

Full name of submitter: Roy Jacobson

Reference (section label): class.prop

Clang bug report: llvm/llvm-project#59206

Issue description:

A class may have a default constructor that is a template while also having a trivial default constructor. The function template default constructor should not prevent the class from being trivial only because it is more constrained.

Consider those classes:

struct A {
  A() = default; //eligible, second constructor unsatisfied
  template<class... Args>
  A(Args&&... args) requires (sizeof...(Args) > 0) {}
};

struct B {
  B() = default; //ineligible, second constructor more constrained
  template<class... Args>
  B(Args&&... args) requires (sizeof...(Args) == 0) {}
};

struct C {
  C() = default; //eligible, but
  template<class... Args> //also eligible and non-trivial
  C(Args&&... args) {}
};

Are those classes trivial? GCC and MSVC think they are, while class.prop says only A is trivial.

As far as I understand it, class C accidentally became non trivial with the resolution of DR1363 but no one actually implemented this.

(Humbly) suggested resolution:

--- source/classes.tex
+++ source/classes.tex
@@ -174,5 +174,5 @@
 \pnum
 A \defnadj{trivial}{class} is a class that is trivially copyable and
-has one or more eligible default constructors\iref{class.default.ctor},
+has one or more eligible default constructors\iref{class.default.ctor} which are not function templates,
 all of which are trivial.
 \begin{note}
@@ -1083,5 +1083,5 @@
 Two special member functions are of the same kind if:
 \begin{itemize}
-\item they are both default constructors,
+\item they are both default constructors and neither or both are function templates,
 \item they are both copy or move constructors
 with the same first parameter type, or

I like it because it preserves the notion of eligibility. I think making function templates just ineligible achieves the same effect and is an alternative solution.

Also somewhat related to CWG issue 1808.

@frederick-vs-ja
Copy link

The standard library type std::ranges::dangling ([range.dangling]) is affected.

namespace std::ranges {
  struct dangling {
    constexpr dangling() noexcept = default;
    constexpr dangling(auto&&...) noexcept {}
  };
}

GCC thinks it's trivial, but Clang (and some new versions of MSVC) doesn't. Per the current specification it's non-trivial, but this seems unintended.

Ping @jensmaurer.

@jensmaurer
Copy link
Member

jensmaurer commented Apr 11, 2024

It seems CWG1808 squarely covers this; see in particular the comment from Feb 2014, including the consideration to simply ditch the concept of "trivial class". (I'm not finding a use of "trivial class" outside of examples.)

Which leads me to the question how you observe the property "trivial class" with a conforming program. You said above your example is considered trivial by gcc, but how do you query that?

I also note that, strictly speaking, a constructor template is not a constructor (a function template is not a function), thus the phrasing "has one or more eligible default constructors" excludes constructor templates.

@jensmaurer jensmaurer changed the title [class.prop] Templated default constructors should not be considered for eligibility CWG1808 [class.prop] Templated default constructors should not be considered for eligibility Apr 11, 2024
@frederick-vs-ja
Copy link

Which leads me to the question how you observe the property "trivial class" with a conforming program. You said above your example is considered trivial by gcc, but how do you query that?

It seems that the property itself only affects the results of std::is_trivial(_v) in conforming programs. Actually, A, C, and dangling shown above are still trivially default constructible, implicit-lifetime types (and thus an object of such a class can have vacuous initialization or be implicitly created).

There's an article (Trivial, but not trivially default constructible by @Quuxplusone) showing that just being a trivial class doesn't mean the class is trivially default constructible.

@Quuxplusone
Copy link

It seems odd that other kinds of SMFs are invariably required to be non-templates:
https://eel.is/c++draft/class.mem#def:copy "A user-declared copy assignment operator X​::​operator= is a non-static non-template member function..."
https://eel.is/c++draft/class.mem#class.copy.ctor-1 "A non-template constructor for class X is a copy constructor if...",
but a default constructor is permitted to be an instantiation of a member function template.

I know no reason to mourn if is_trivial were to go the way of is_pod and is_literal_type. In fact is_trivial is highly analogous to is_literal_type: it queries the compiler on the academic point of whether some given member exists, without any guarantee that the given member is even callable or usable for any purpose at all.

If is_trivial were to go away, then would anything still depend on the formal definition of "default constructor", or could that go away too? (I've been teaching "it should have been called the zero-argument constructor" for years anyway.)

There is implementation divergence on

template<class... Ts>
struct S {
    S(Ts...) = default;
};
S<> s;

According to the letter of the law, I think S<>::S() is supposed to be well-formed (as per Clang); my second guess is that it's supposed to be defaulted-as-deleted because its parameter list isn't empty but rather contains a pack; I don't think it should be ill-formed (as per EDG+MSVC+GCC) under any interpretation.

Related to Roy's test cases, but much simpler, every vendor seems to miscompile the following case according to the letter of the law. The wording says: "If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted"—

struct NoCtors {
    // This class has no constructors; it has only a constructor template.
    // Yet in practice nobody generates a defaulted zero-argument constructor for it!
    NoCtors(auto...);
};
NoCtors nc;  // Everyone calls the user-provided NoCtors::NoCtors<>(),
  // not the defaulted constructor that should have been generated per [class.default.ctor]/1

@frederick-vs-ja
Copy link

There is implementation divergence on

template<class... Ts>
struct S {
    S(Ts...) = default;
};
S<> s;

According to the letter of the law, I think S<>::S() is supposed to be well-formed (as per Clang); my second guess is that it's supposed to be defaulted-as-deleted because its parameter list isn't empty but rather contains a pack; I don't think it should be ill-formed (as per EDG+MSVC+GCC) under any interpretation.

As discussed in #150, this example is ill-formed, no diagnostic required. So both rejection and acceptance are OK.

Related to Roy's test cases, but much simpler, every vendor seems to miscompile the following case according to the letter of the law. The wording says: "If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted"—

struct NoCtors {
    // This class has no constructors; it has only a constructor template.
    // Yet in practice nobody generates a defaulted zero-argument constructor for it!
    NoCtors(auto...);
};
NoCtors nc;  // Everyone calls the user-provided NoCtors::NoCtors<>(),
  // not the defaulted constructor that should have been generated per [class.default.ctor]/1

This is CWG2871.

@Quuxplusone
Copy link

There is implementation divergence on

template<class... Ts>
struct S {
    S(Ts...) = default;
};
S<> s;

According to the letter of the law, I think S<>::S() is supposed to be well-formed (as per Clang); my second guess is that it's supposed to be defaulted-as-deleted because its parameter list isn't empty but rather contains a pack; I don't think it should be ill-formed (as per EDG+MSVC+GCC) under any interpretation.

As discussed in #150, this example is ill-formed, no diagnostic required. So both rejection and acceptance are OK.

Bleh. OK. :)
As long as I'm off-topic, I might as well mention two other big tables of =default-related implementation divergence that CWG might be unaware of. Do with these examples what you will:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2952r1.html#corner-cases
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2953r0.html#corner-cases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants