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

Why do neither reference types nor array types satisfy Destructible? #70

Closed
CaseyCarter opened this issue Jul 29, 2015 · 7 comments
Closed

Comments

@CaseyCarter
Copy link
Collaborator

CaseyCarter commented Jul 29, 2015

An email from Walter Brown asks (I assume Walter would not mind me reposting the question here):

I must be missing something: why do we want neither reference types nor array types to satisfy Destructible<>()? Surely variables of such types can be destroyed.

I understand that Destructible is considered “the base of the hierarchy of object concepts” — but if it’s really needed, why isn’t there a SingleObject<>() concept for this purpose? Or, better yet, just thusly rename the current Destructible?

Proposed Resolution

See P0547R1.

@CaseyCarter
Copy link
Collaborator Author

Primarily because we want to guarantee that t.~T() is valid for models of Destructible, as is the case for the Destructible concept in the Standard. That syntax is valid only for non-array object types. (For reference, the standard library uses Destructible in NullablePointer, Hash, unique_ptr's deleter, TrivialClock's rep, duration and time_point members, Iterator, and the "mutex types" [thread.mutex.requirements.mutex]. It should probably explicitly require Destructible for std::allocator::destroy as well since it may not throw exceptions and its effects clause uses the ~T() syntax.)

As is the case for many of the library concepts, Destructible is meant to be slightly stronger than the core language concept embodied by the type trait is_destructible. In general, stronger requirements for library concepts allow implementations to have less special-case-handling code so that we don't all have to pay compile and run time costs for pathological corner cases.

There's a strong argument that Destructible should accept reference types - which are always destructible - for consistency with Constructible so that Constructible<T, Args...> subsumes Destructible<T>. We've avoided that thus far since (a) the fact that Constructible<T> implies that T is nothrow_destructible is generally sufficient, and (b) introducing disjunctions into concept definitions has undesirable compile time consequences.

@W-E-Brown
Copy link

On Jul 29, 2015, at 3:33 PM, Casey Carter notifications@github.com wrote:

There's a strong argument that Destructible should accept reference types - which are always destructible - for consistency with Constructible so that Constructible subsumes Destructible. We've avoided that thus far since (a) the fact that Constructible implies that T is nothrow_destructible is generally sufficient, and (b) introducing disjunctions into concept definitions has undesirable compile time consequences.


Reply to this email directly or view it on GitHub.

ISTM we can avoid at least some disjunctions via a conditional expression:

template< class T, class... Args >
concept bool
Constructible( )
{
return is_reference_v
? __BindableReference()
: __ConstructibleObject()
;
}

— WEB

@ericniebler
Copy link
Owner

WEB writes:

ISTM we can avoid at least some disjunctions via a conditional expression:

Does that really avoid the problem? Would not a disjunction by another name smell as sweet?

@W-E-Brown
Copy link

On Aug 3, 2015, at 1:37 PM, Eric Niebler notifications@github.com wrote:

WEB writes:

ISTM we can avoid at least some disjunctions via a conditional expression:

Does that really avoid the problem?

It might, IMO, else I would not have written. See below.

Would not a disjunction by another name smell as sweet?

Possibly, but not necessarily.

It’s been reported (e.g., earlier in this issue) that disjunctions (which I interpret as or-expressions) tend to degrade performance. However, I have not seen reports from any experiment that explores the algorithmic performance of conditional-expressions.

Until we have hard data, it is at least plausible to me that the underlying algorithm may process an or-expression differently than it does a conditional-expression. ISTM to be worth an experiment that explores the conjecture, and then we’ll know.

— WEB

@CaseyCarter
Copy link
Collaborator Author

It’s been reported (e.g., earlier in this issue) that disjunctions (which I interpret as or-expressions) tend to degrade performance. However, I have not seen reports from any experiment that explores the algorithmic performance of conditional-expressions.

Until we have hard data, it is at least plausible to me that the underlying algorithm may process an or-expression differently than it does a conditional-expression. ISTM to be worth an experiment that explores the conjecture, and then we’ll know.

Specifically, our definition of Constructible as __ConstructibleObject<T, Args...> || __BindableReference<T, Args...> wreaked havoc with the prototype implementation. The compiler would fill all available memory and then ICE (See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66962) simply compiling the header with the iterator concepts and primitives. Apparently the size of the normalized constraints grows quadratically with the number of disjunctions, so disjunctions in low-level concepts are not good. Until the GCC team - and I'll name Jason Merrill in particular who has been putting a ton of work into the concepts branch - figures out if/how to address this issue the workaround is avoid disjunctions in concept definitions as much as possible.

For our implementation of Constructible, I hid the disjunction inside a trait (diff is here) so that the constraint language sees an atomic predicate equivalent to foo_trait<T, Args...>::value instead of ConstructibleObject<...> || BindableReference.<...>. The simplest way to convert A || B from a disjunction of constraints into an atomic predicate is probably to replace it with !!(A || B) as discussed in the GCC PR. Your suggestion of using a conditional expression would have the same effect:

is_reference_v<T> ? BindableReference<T, Args...>() : ConstructibleObject<T, Args...>()

is an atomic predicate equivalent to !!(BindableReference<T, Args...>() || ConstructibleObject<T, Args...>())

"Hiding" the disjunction inside an atomic predicate addresses the compiler performance issue, but converting e.g. A || B into e.g. !!(A || B) has consequences for subsumption of constraints. The compiler knows that A subsumes A || B (and likewise B subsumes A || B) but it can determine no such relation between A and !!(A || B) (respectively B and !!(A || B)). Consequently the overloads:

void f() requires A;
void f() requires !!(A || B);

can't be disambiguated when A is satisfied. This isn't a problem for Constructible - nothing needs to subsume Constructible - but it does illustrate that hiding disjunctions inside atomic predicates has important design consequences.

@asutton
Copy link
Contributor

asutton commented Aug 4, 2015

I added a patch to the issue in Bugzilla last week fyi.

On Mon, Aug 3, 2015, 23:03 Casey Carter notifications@github.com wrote:

It’s been reported (e.g., earlier in this issue) that disjunctions (which
I interpret as or-expressions) tend to degrade performance. However, I have
not seen reports from any experiment that explores the algorithmic
performance of conditional-expressions.

Until we have hard data, it is at least plausible to me that the
underlying algorithm may process an or-expression differently than it does
a conditional-expression. ISTM to be worth an experiment that explores the
conjecture, and then we’ll know.

Specifically, our definition of Constructible as __ConstructibleObject<T,
Args...> || __BindableReference<T, Args...> wreaked havoc with the
prototype implementation. The compiler would fill all available memory and
then ICE (See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66962) simply
compiling the header with the iterator concepts and primitives. Apparently
the size of the normalized constraints grows quadratically with the number
of disjunctions, so disjunctions in low-level concepts are not good. Until
the GCC team - and I'll name Jason Merrill in particular who has been
putting a ton of work into the concepts branch - figures out if/how to
address this issue the workaround is avoid disjunctions in concept
definitions as much as possible.

For our implementation of Constructible, I hid the disjunction inside a
trait (diff is here
CaseyCarter/cmcstl2@f4de216#diff-e6e4a3ccd56fe38d79cae3787d554f46R49)
so that the constraint language sees an atomic predicate equivalent to foo_trait<T,
Args...>::value instead of ConstructibleObject<...> ||
BindableReference.<...>. The simplest way to convert A || B from a
disjunction of constraints into an atomic predicate is probably to replace
it with !!(A || B) as discussed in the GCC PR. Your suggestion of using a
conditional expression would have the same effect:

is_reference_v ? BindableReference<T, Args...>() : ConstructibleObject<T, Args...>()

is an atomic predicate equivalent to !!(BindableReference<T, Args...>()
|| ConstructibleObject<T, Args...>())

"Hiding" the disjunction inside an atomic predicate addresses the compiler
performance issue, but converting e.g. A || B into e.g. !!(A || B) has
consequences for subsumption of constraints. The compiler knows that A
subsumes A || B (and likewise B subsumes A || B) but it can determine no
such relation between A and !!(A || B) (respectively B and !!(A || B)).
Consequently the overloads:

void f() requires A;
void f() requires !!(A || B);

can't be disambiguated when A is satisfied. This isn't a problem for
Constructible - nothing needs to subsume Constructible - but it does
illustrate that hiding disjunctions inside atomic predicates has important
design consequences.


Reply to this email directly or view it on GitHub
#70 (comment).

@ericniebler
Copy link
Owner

Alisdair sees "Destructible" as meaning "can fall out of scope." "Deletable" might mean, "can call the destructor explicitly". Think about naming, also for the section. References are not objects, so why are these Object Concepts?

@ericniebler ericniebler added the P1 label Mar 4, 2017
@CaseyCarter CaseyCarter added review and removed new labels Mar 5, 2017
@ericniebler ericniebler added ready and removed review labels Jul 12, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
CaseyCarter added a commit that referenced this issue Jul 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants