C++ Logo


Advanced search

Subject: Concepts & Incomplete Types
From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2020-06-19 17:11:58

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;
     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.


Paul Mensonides

STD-DISCUSSION list run by herb.sutter at gmail.com

Older Archives on Google Groups