Requirement Propagation and CopyConstructible

By Douglas Gregor

The concepts proposal contains a feature called “requirement
propagation” (formerly referred to as “constraint propagation”), which
implicitly defines constraints based on the declaration of a
template. If you didn’t know that requirement propagation existed,
don’t worry; it’s a behind-the-scenes feature that reduces the size of
requirements clauses.

There is one aspect of requirement propagation that we would like to
change. As of N2193, [temp.req.prop] paragraph 2 says:

For every type T that appears as an argument or return type in a
function declarator, the requirement std::MoveConstructible<T> is
implicitly added to the requirements clause. [ Example:

    template<EqualityComparable T>
    bool eq(T x, T y); // implicitly adds requirement CopyConstructible<T>

--end example ]

For various reasons, this feature is problematic and has been removed from ConceptGCC. This change will break some “existing” concepts code. For example, the function “negate” below is currently (according to N2193) well-formed:

  auto concept Negatable<typename T> {
    T operator-(T);
  }

  template<Negatable T>
  T negate(const T& x) { return -x; }

ConceptGCC used to compile this. With today’s development version of ConceptGCC, however, we get an error message:

  beep.C: In function 'T negate(const T&)':
  beep.C:6: error: constructor 'T::T(const T&)' is inaccessible

To remedy the error, add a CopyConstructible requirement, so that “negate” can return its value. Formerly, the
compiler would have added this requirement:

  template<Negatable T> requires CopyConstructible<T>
  T negate(const T& x) { return -x; }

4 Responses to “Requirement Propagation and CopyConstructible”

  1. gfaraj Says:

    Wait… I’m a little confused. Why doesn’t ConceptGCC compile the negate template as firstly shown? Why do we need to specify the CopyConstructible requirement, when the return type of negate implicitly states that T must be copy constructible? Maybe I’m misunderstanding the post, can someone clarify, please? Thanks!

  2. Niels Dekker Says:

    Thanks for your information, Doug! Like gfaraj, I’d be interested to know why this “requirement propagation” had to be removed. It seemed to me like a very convenient feature! But you got me even more confused, because your quote from N2193, [temp.req.prop] paragraph 2, differs slightly from the paper at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2193.pdf

    n2193.pdf says:
    bool eq(T x, T y); // implicitly adds requirement MoveConstructible

    Do you agree that once the MoveConstructible concept is included with ConceptGCC, ‘T negate(const T&)’ should require MoveConstructible, instead of CopyConstructible?

  3. Douglas Gregor Says:

    Sorry, I wrote up all of the rationale behind the removal of this feature in a note to the C++ committee, but did not include the motivation here. Here’s an excerpt with the motivation for this decision:

    Previously (and in ConceptGCC), we propagated the requirement
    std::CopyConstructible<T> for argument and return types. With the
    introduction of rvalue references, we changed this to
    std::MoveConstructible<T>, because one can move values into arguments
    and out of the return type. This change has exposed two issues:

    1) Users—not the compiler—should be deciding between
    MoveConstructible and CopyConstructible.

    2) If future changes to C++ mean that one can construct arguments
    without requiring a copy or move constructor, we would either
    have our templates over-constrained, or we would have to break
    code that relies on requirement propagation. Both are bad
    outcomes.

    Due to these problems, and the coupling of the MoveConstructible
    concept with the compiler, we want to remove requirement propagation
    of MoveConstructible from argument and return types in a declaration.

  4. gfaraj Says:

    Thanks for the clarification Douglas. I understand now the reasoning of removing the compiler-generated requirements. It does make sense and goes along with the usual desire of the C++ community to reduce the amount of coupling between the language and the standard library. Thanks again.

Leave a Reply