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
};
'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