C++ Logo

std-proposals

Advanced search

[std-proposals] adding declaration requirement to 'requires'-expression requirement

From: Desmond Gold <desmondbongcawel922_at_[hidden]>
Date: Sat, 30 Nov 2024 22:10:21 +0800
This proposal introduces a *declaration requirement* in the body of a
'requires' expression.

motivation:

+ It is frustrating to not have the declaration to be SFINAE-friendly.
Hence, you can't check whether the structured binding is valid or not. This
resorts to having hacky workarounds such as what boost.pfr did.

+ We didn't have features before in SFINAE to check for valid variable
initialization except for pr-value object construction or placement-new
expression

+ It is also frustrating to use lengthy types
  to be used in type requirements.

  requires {
    requires ValidOne<typename foo<T>::here_with<U>::there>;
    requires ValidTwo<typename foo<T>::here_with<U>::there>;
  };

proposal:

+ adding 'declaration requirement' as one of the requirements in body
  of a 'requires' expression.

+ Declaration requirement (decl-req.)
  - type alias declaration
  - simple declaration (variable and structured binding declarations)
    - no linkage, storage, or lifetime
    - 'constexpr' and 'constinit' not allowed
    - allows initializers (unevaluated)
    - it also allows constrained auto as an additional nested requirement
  - does not allow attributes
  - the declaration requirement checks for valid declaration and after
that, it introduces a new name in that declaration.
    - the initialization are all unevaluated
    - this name can be used in subsequent requirements within the same body
of 'requires' expression

examples:

*(assume all these requires-expressions are within the template context)*

1.) introduces type alias

requires {
  // requires the type to be valid and then introduces 'There'
  using There = foo<T>::here_with<U>::there;
  requires ValidOne<There>;
  requires ValidTwo<There>;
};

2.) variables introduced in decl-req. are not the same as local parameters

requires (A a, B b) {
  do_something(a, b);
};

requires {
  // this requires 'a' and 'b' to be default initialized
  A a;
  B b;
  do_something(a, b);
};

3.) with initializers and auto

requires {
  A a {}; // ok
  A _ {}; // ok
  B _ {}; // placeholder variable permitted, ok
  { use_this(a) } -> valid_return; // ok
  { use_this(_) } -> valid_return; // error, ambiguous _

  // functions pointers allowed
  auto (* fp) (A, B) -> void;

  auto c = return_this(); // ok
  some_constraint auto d = return_this(); // ok, checks if 'd' satisfies
'some_constraint'

  A& e = std::declval<A&>(); // allows unevaluated initializers
};

4.) structured bindings

requires {
  // checks if 'T' can be decomposed into three elements
  auto [a, b, c] = std::declval<T>();
};

template <typename T>
concept decomposable = requires
{
  auto [...args] = std::declval<T>();
  // 'args' pack can be used in other requirements
  // this is only allowed when the initializer is templated entity
};

5.) constexpr not allowed

requires
{
  T t; // ok, if that initialization is allowed
  const U u; ok, if that initialization is allowed
  constexpr V v = some_initializer(); // error, 'constexpr' not allowed

  use_nttp<N>; // ok, if 'N' is some non-type template parameter
  use_nttp<u>; // error, unevaluated variable 'u' cannot be used as nttp

  use_ttp<decltype(u)>; // ok
};

Received on 2024-11-30 14:11:39