C++ Logo

std-discussion

Advanced search

Re: Concepts & Incomplete Types

From: Ville Voutilainen <ville.voutilainen_at_[hidden]>
Date: Sat, 20 Jun 2020 02:53:52 +0300
On Sat, 20 Jun 2020 at 01:12, Paul Mensonides via Std-Discussion
<std-discussion_at_[hidden]> wrote:
>
> What is the intended behavior of the following code?
>
> #include <concepts>
>
> // ----
>
> class base { };
> class derived : public base { };
>
> template<std::derived_from<base> T> void f(const T*) { }
>
> // ----
>
> class other; // note: incomplete
>
> void f(const other*) { }
>
> int main() {
> other* p = nullptr;
> f(p);
> return 0;
> }
>
> AFAICT, the above should produce a diagnostic as the evaluation of the
> concept "std::derived_from<T, base>" where T is "other" causes the
> instantiation of std::is_base_of with an incomplete type. I.e. the
> declaration of the constrained template "f" appears to poison the well.
>
> This is a problem with "std::enable_if" as well, which is why I use an
> alternate "enable_if" whenever the checks I am doing require a complete
> type, such as:
>
> #include <type_traits>
>
> template<bool P, class T = void, class... Q> struct enable_if { };
> // ^^^^^^^^^^ lazy predicates
>
> template<class T> struct enable_if<true, T> {
> using type = T;
> };
>
> template<class T, class P, class... Q>
> struct enable_if<true, T, P, Q...> : enable_if<P::value, T, Q...> { };
>
> template<bool P, class T = void, class... Q>
> using enable_if_t = typename enable_if<P, T, Q...>::type;
>
> // ----
>
> template<class T>
> enable_if_t<sizeof(T), void, std::is_base_of<base, T>> f(const T*) { }
> // ^^^^^^^^^ causes substitution failure if T is incomplete
> // prior to any instantiation (avoiding ODR issues)
>
> There doesn't seem to be a way to replicate this within a concept due to
> the sentence:
>
> "If, at different points in the program, the satisfaction result is
> different for identical atomic constraints and template arguments, the
> program is ill-formed, no diagnostic required."
>
> E.g. something like:
>
> template<class T, class U>
> concept derived_from = !!sizeof(T) && std::derived_from<T, U>;
>
> The constraint "!!sizeof(T)" would evaluate differently should "other"
> become complete (in any translation unit) and the constraint
> "derived_from<other, base>" would evaluate differently should other
> become complete _and_ derived from "base" (in any translation unit). In
> both cases, causing the program to become ill-formed.
>
> // ----
>
> For the moment, I am assuming that one would need to supplement the
> concept mechanism with SFINAE such as in the following
>
> template<std::derived_from<base> T>
> std::enable_if_t<sizeof(T)> f(const T*) { }
>
> which seems unfortunate.
>
> Thoughts?

Plenty. Concepts weren't designed to solve this problem in any way
different than
template equivalence in general wasn't. You can't write a metaprogram
that detects
an incomplete type if that metaprogram is ever run later with a complete type,
because ODR and the general notion of what [temp.point]/7 is about.

I understand it's unfortunate. We need to think carefully about a
solution for this problem.

Received on 2020-06-19 18:57:15