C++ Logo

std-proposals

Advanced search

Re: is_complete type trait?

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Fri, 12 Nov 2021 17:45:35 +0000
On Fri, 12 Nov 2021 12:04:41 -0500
Arthur O'Dwyer via Std-Proposals <std-proposals_at_[hidden]> wrote:

> On Fri, Nov 12, 2021 at 8:07 AM Lénárd Szolnoki via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> > On Fri, 12 Nov 2021 00:50:21 +0100
> > Bjorn Reese via Std-Proposals <std-proposals_at_[hidden]>
> > wrote:
> > > Has there been any proposals to add a type trait to check if a
> > > type is complete?
> >
> > I mainly agree with others that this is a footgun and it simply
> > can't be standardized at least in the same form as the other traits.
> >
>
> I also agree that it shouldn't be standardized, because footgun.
> Also, notice that almost always, when you think you want a complete
> type, really what you want is a complete *object* type (not a
> reference type and not a function type). So merely something like
> `concept std::complete` wouldn't be completely what you want, either
> in the STL or in your own code.
>
> However, I have become ambivalent on the footgun argument, because it
> actually applies to many Ranges concepts that are even more
> complicated than `is_complete`, and yet we standardized those.
> Consider e.g. https://godbolt.org/z/EMsv4h19K
>
> struct S;
> inline bool obviously_false() { return std::ranges::view<S>; }
>
> struct S : std::string_view {};
>
> template<>
> constexpr bool std::ranges::enable_view<S> = true;
>
> static_assert(!std::ranges::range<S>);
> static_assert(!std::ranges::view<S>);
>
> This is a hard error on GCC, and compiles without errors on Clang and
> MSVC.
>
> I suspect that it's possible to construct (or run-into-in-the-wild)
> some less contrived examples of this; maybe by using CRTP base
> classes; or having member functions that return types like
> `std::ranges::filter_view<Myself>`, where, as a side effect of naming
> that type, some concept such as `view<Myself>` is prematurely "locked
> in" with the wrong value. I would be interested in seeing such
> examples, if anyone runs into one.
>
> I also think that, like a lot of stuff in Ranges, this is probably
> "not a problem" if you look at it from exactly the right angle.
> Ranges has lots of new idioms that you have to look at in exactly the
> right way for them to make sense, e.g.
> - always pass ranges by forwarding-reference (but do not forward them)
> - in particular, never pass by const-reference
> - yes dangling is a problem, but you can mitigate that by never
> prematurely dissecting a range object into an iterator-pair
> - yes prematurely "locking in" concepts is a problem, but you can
> mitigate that by... (fill in the blank)
> Therefore I suspect that having a `std::complete_object_type` concept
> is no bigger of a problem than `concept std::ranges::view` — either
> they are both problematic, or neither of them is problematic. My
> current understanding is that they're both problematic; but I suspect
> that there's someone out there, more up on their Ranges idioms, who
> could explain why* if you look at it from the right angle* neither of
> them is problematic, and could give some best practices for dealing
> with them both.
>
> my $.02,
> –Arthur

Hi,

I think a reasonable answer for your example would be something like:
* don't use the trait/concept on incomplete class types
* don't use primary template (directly or indirectly) before a
  specialization could be selected for the same arguments (referring to
  enable_view here)

The first point would be a hard sell on something like `is_complete`,
as it would just rule out the interesting cases.
Also under the current wording as a trait it would just be UB in these
cases (https://eel.is/c++draft/meta#rqmts-5).

It would be nice if the existing traits could be hardened in a way that
they would be ill-formed instead of UB in the problematic cases, but
even then `is_complete` would be just ill-formed in the interesting
cases, so not really useful.

A more useful "trait" would be something like an eagerly evaluated
`__is_complete(T)` (not suggesting this name), that doesn't risk
ODR-violation at least on the trait itself. Otherwise I think even a
concept is subject to ODR rules to evaluate to the same thing within a
TU, but I'm not a 100% sure on that. I think I saw cached concept
results when I played around with that.

I'm also somewhat ambivalent on the footgun aspect, you can certainly
shoot yourself in the foot with incomplete types without any standard
traits/concepts or even writing your own SFINAE madness:

struct Base {};
#if defined(TU1)
struct Derived;
#elif defined(TU2)
struct Derived{};
#endif

void foo(void*); // 1
void foo(Base*); // 2

inline void bar(Derived* ptr) {
    foo(ptr); // calls (1) in TU1, calls (2) in TU2
}

Cheers,
Lénárd

Received on 2021-11-12 11:45:47