Document number: | P0013R1 |
Date: | 2015-10-23 |
Project: | Programming Language C++, Library Evolution Working Group |
Reply-to: | Jonathan Wakely <cxx@kayari.org> |
and_
, or_
and not_
following
LEWG design review.
I propose three new type traits for performing logical operations with other
traits, conjunction
, disjunction
, and negation
,
corresponding to the operators
&&
, ||
, and !
respectively.
These utilities are used extensively in libstdc++ and are valuable additions
to any metaprogramming toolkit.
// logical conjunction: template<class... B> struct conjunction; // logical disjunction: template<class... B> struct disjunction; // logical negation: template<class B> struct negation;
The proposed traits apply a logical operator to the result of one or more
type traits, for example the specialization
conjunction<is_copy_constructible<T>, is_copy_assignable<T>>
has a BaseCharacteristic of true_type
only if T
is copy constructible and copy assignable, or in other words it has a
BaseCharacteristic equivalent to
bool_constant<is_copy_constructible_v<T> && is_copy_assignable_v<T>>
.
The traits are especially useful when dealing with variadic templates
where you want to apply the same predicate or transformation to every
element in a parameter pack, for example
disjunction<is_nothrow_default_constructible<T>...>
can be used to determine whether at least one of the types in the pack
T
has a non-throwing default constructor.
Practicing metaprogrammers often consider making more of our unary type traits
and/or type relationship traits variadic; a request that arises
seemingly very often is having something like 'are_same' which would be a
variadic is_same
.
The proposed conjunction trait allows turning any trait into
a variadic trait,
without adding a plethora of variadic counterparts for individual
traits that we already have.
The disjunction and negation complete the set.
Quoth Ville: "__and_
, __or_
and __not_
as they are already available in libstdc++ are an absolute godsend, not
just for a library
writer, but for any user who needs to do metaprogramming with parameter packs.
Boost.MPL has shipped similar facilities for a very long time, and
it's high time we standardize these simple utilities."
To demonstrate how to use conjunction
with is_same
to
make an 'all_same' trait, constraining a variadic function template so that
all arguments have the same type can be done using
conjunction<is_same<T, Ts>...>
, for example:
// Accepts one or more arguments of the same type.
template<typename T, typename... Ts>
enable_if_t< conjunction_v<is_same<T, Ts>...> >
func(T, Ts...)
{ }
For the sake of clarity this function doesn't do perfect forwarding,
but if the parameters were forwarding references the constraint would only
be slightly more complicated:
conjunction_v<is_same<decay_t<T>, decay_t<Ts>>...>
.
Constraining all elements of a parameter pack to a specific type can be done similarly:
// Accepts zero or more arguments of type int.
template<typename... Ts>
enable_if_t< conjunction_v<is_same<int, Ts>...> >
func(Ts...)
{ }
Of course the three traits can be combined to form arbitrary predicates,
the specialization
conjunction<disjunction<foo, bar>, negation<baz>>
corresponds to
(foo::value || bar::value) && !baz::value
.
This is a pure addition with no dependency on anything that isn't already in the 2014 standard. I proposed it for inclusion in the Library Fundamentals TS rather than the International Standard, but in Kona LEWG proposed adding it to both.
In this proposal the class templates conjunction
and disjunction
derive from their arguments. An alternative design would be to force a
BaseCharacteristic of
true_type
or false_type
, i.e. instead of the
proposed design:
template<class B1> struct conjunction<B1> : B1 { };
we could specify it as:
template<class B1> struct conjunction<B1> : bool_constant<B1::value> { };
I believe the former is more flexible and preserves more information.
We could require the template arguments themselves to have a
BaseCharacteristic of true_type
or
false_type
but I think that would be an unnecessary restriction,
they only really require B::value
to be convertible to bool.
As proposed the traits will work with user-defined traits that have a nested
value
member of an enumeration type and other forms of trait
that don't derive from a specialization of integral_constant
.
The variable templates conjunction_v
, disjunction_v
, and
negation_v
do not exist in libstdc++, largely because these traits
predate G++'s support for variable templates by several years. However the
examples above demonstrate their usefulness and adding them is consistent
with the other variable templates proposed by
N3854.
Many uses of these traits with parameter packs can be replaced by
fold expressions, for example
conjunction_v<T...>
can be replaced by
(true && ... && T::value)
,
however the fold expression will instantiate T::value
for every
element of the pack,
whereas the the proposed behaviour of conjunction
and disjunction
only instantiates as many elements of the pack as necessary to determine the
answer, i.e. they perform short-circuiting with regard to instantiations.
This short-circuiting makes
conjunction<is_copy_constructible<T>, is_copy_assignable<T>>
potentially cheaper to instantiate than the logically equivalent
bool_constant<is_copy_constructible_v<T> && is_copy_assignable_v<T>>
.
Efficiency aside, the short-circuiting property allows these traits to be
used in contexts that would be more difficult otherwise. If we have a
trait is_foo
such that is_foo<T>::value
is ill-formed unless T
is a class type, the expression
is_class_v<T> && is_foo<T>::value
would be unsafe and must be replaced by something using a extra level of
indirection to ensure is_foo::value
is only instantiated when
valid, such as
conditional_t<is_class_v<T>, is_foo<T>, false_type>::value
.
This is almost precisely what
conjunction_v<is_class<T>, is_foo<T>>
expands to, but conjunction_v
expresses the intention more clearly.
The traits were originally proposed with the obvious names,
"and", "or" and "not", adjusted to avoid clashing with the
alternative tokens of the same names.
Another option would have been static_and
but the context and
angle brackets should be enough to make it obvious these are templates that
are evaluated at compile-time, and the shorter names are easier to read.
LEWG discussed a number of names, and the eventual preference was for
longer names, without a trailing underscore.
LEWG discussed putting these into a new sub-namespace for metaprogramming
tools, provisionally called meta
, but there was not consensus
to do that at this time. The author intends to propose a more complete
toolkit in such a namespace at some point.
For the purposes of SG10, I recommend feature-testing macros named__cpp_lib_logical_traits
and__cpp_lib_experimental_logical_traits
for the IS and TS respectively.
If the variable templates for type traits are accepted into the C++ working paper then the editor is requested to also addconjunction_v
,disjunction_v
andnegation_v
as defined below for the Library Fundamentals TS.
Add to the synopsis in [meta.type.synop]:
// [meta.logical], logical operator traits: template<class... B> struct conjunction; template<class... B> struct disjunction; template<class B> struct negation;
Add a new subclause in [meta]:
3.3.x Logical operator traits [meta.logical]This subclause describes type traits for applying logical operators to other type traits.
template<class... B> struct conjunction : see below { };
The class template conjunction
forms the logical conjunction of its
template type arguments.
Every template type argument shall be usable as a base class and shall have
a member value
which is convertible to bool
,
is not hidden, and is unambiguously available in the type.
The BaseCharacteristic of a specialization conjunction<B1, ..., BN>
is the first type Bi
in the list
true_type, B1, ..., BN
for which Bi::value == false
, or if every
Bi::value != false
the BaseCharacteristic is BN
.
[Note:
This means a specialization of conjunction
does not necessarily
have a BaseCharacteristic of either true_type
or
false_type
.
— end note]
For a specialization
conjunction<B1, ..., BN>
if there is a template type argument Bi
with
Bi::value == false
then instantiating
conjunction<B1, ..., BN>::value
does not require the instantiation of Bj::value
for j > i
.
[Note:
This is analogous to the short-circuiting behavior of &&
.
— end note]
template<class... B> struct disjunction : see below { };
The class template disjunction
forms the logical disjunction of its
template type arguments.
Every template type argument shall be usable as a base class and shall have
a member value
which is convertible to bool
,
is not hidden, and is unambiguously available in the type.
The BaseCharacteristic of a specialization disjunction<B1, ..., BN>
is the first type Bi
in the list
false_type, B1, ..., BN
for which Bi::value != false
, or if every
Bi::value == false
the BaseCharacteristic is BN
.
[Note:
This means a specialization of disjunction
does not necessarily
have a BaseCharacteristic of either true_type
or
false_type
.
— end note]
For a specialization
disjunction<B1, ..., BN>
if there is a template type argument Bi
with
Bi::value != false
then instantiating
disjunction<B1, ..., BN>::value
does not require the instantiation of Bj::value
for j > i
.
[Note:
This is analogous to the short-circuiting behavior of ||
.
— end note]
template<class B> struct negation : bool_constant<!B::value> { };
The class template negation
forms the logical negation of its
template type argument. The type negation<B>
is a
UnaryTypeTrait with a BaseCharacteristic of
bool_constant<!B::value>
.
These changes are the same as the changes for the C++ working draft, except thatbool_constant
is replaced byintegral_constant
, becausebool_constant
is not in the TS, and the_v
variable templates are added.
Add to the synopsis in [meta.type.synop]:
// [meta.logical], logical operator traits: template<class... B> struct conjunction; template<class... B> constexpr bool conjunction_v = conjunction<B...>::value; template<class... B> struct disjunction; template<class... B> constexpr bool disjunction_v = disjunction<B...>::value; template<class B> struct negation; template<class B> constexpr bool negation_v = negation<B>::value;
Add a new subclause in [meta]:
3.3.x Logical operator traits [meta.logical]This subclause describes type traits for applying logical operators to other type traits.
template<class... B> struct conjunction : see below { }; template<class... B> constexpr bool conjunction_v = conjunction<B...>::value;
The class template conjunction
forms the logical conjunction of its
template type arguments.
Every template type argument shall be usable as a base class and shall have
a member value
which is convertible to bool
,
is not hidden, and is unambiguously available in the type.
The BaseCharacteristic of a specialization conjunction<B1, ..., BN>
is the first type Bi
in the list
true_type, B1, ..., BN
for which Bi::value == false
, or if every
Bi::value != false
the BaseCharacteristic is BN
.
[Note:
This means a specialization of conjunction
does not necessarily
have a BaseCharacteristic of either true_type
or
false_type
.
— end note]
For a specialization
conjunction<B1, ..., BN>
if there is a template type argument Bi
with
Bi::value == false
then instantiating
conjunction<B1, ..., BN>::value
does not require the instantiation of Bj::value
for j > i
.
[Note:
This is analogous to the short-circuiting behavior of &&
.
— end note]
template<class... B> struct disjunction : see below { }; template<class... B> constexpr bool disjunction_v = disjunction<B...>::value;
The class template disjunction
forms the logical disjunction of its
template type arguments.
Every template type argument shall be usable as a base class and shall have
a member value
which is convertible to bool
,
is not hidden, and is unambiguously available in the type.
The BaseCharacteristic of a specialization disjunction<B1, ..., BN>
is the first type Bi
in the list
false_type, B1, ..., BN
for which Bi::value != false
, or if every
Bi::value == false
the BaseCharacteristic is BN
.
[Note:
This means a specialization of disjunction
does not necessarily
have a BaseCharacteristic of either true_type
or
false_type
.
— end note]
For a specialization
disjunction<B1, ..., BN>
if there is a template type argument Bi
with
Bi::value != false
then instantiating
disjunction<B1, ..., BN>::value
does not require the instantiation of Bj::value
for j > i
.
[Note:
This is analogous to the short-circuiting behavior of ||
.
— end note]
template<class B> struct negation : integral_constant<bool, !B::value> { }; template<class B> constexpr bool negation_v = negation<B>::value;
The class template negation
forms the logical negation of its
template type argument. The type negation<B>
is a
UnaryTypeTrait with a BaseCharacteristic of
integral_constant<bool, !B::value>
.
Example implementations of and_
and or_
(the originally proposed names) based on
Daniel Krügler's code in libstdc++ are shown here:
template<class...> struct and_; // not defined
template<> struct and_<> : true_type { };
template<class B1> struct and_<B1> : B1 { };
template<class B1, class B2>
struct and_<B1, B2>
: conditional_t<B1::value, B2, B1>
{ };
template<class B1, class B2, class B3, class... Bn>
struct and_<B1, B2, B3, Bn...>
: conditional_t<B1::value, and_<B2, B3, Bn...>, B1>
{ };
template<class...> struct or_; // not defined
template<> struct or_<> : false_type { };
template<class B1> struct or_<B1> : B1 { };
template<class B1, class B2>
struct or_<B1, B2>
: conditional_t<B1::value, B1, B2>
{ };
template<class B1, class B2, class B3, class... Bn>
struct or_<B1, B2, B3, Bn...>
: conditional_t<B1::value, B1, or_<B2, B3, Bn...>>
{ };
Thanks to Daniel Krügler for the original implementation in libstdc++ which inspired this proposal, and to Ville Voutilainen for nudging me to write it up and for providing part of the motivation section.
N3854, Variable Templates For Type Traits, by Stephan T. Lavavej.