This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
std::complex
Section: 28.4.3 [complex] Status: New Submitter: Jiang An Opened: 2023-05-16 Last modified: 2023-06-01
Priority: 4
View other active issues in [complex].
View all other issues in [complex].
View all issues with New status.
Discussion:
In C++20 and earlier revisions, there are constructors taking two floating-point numbers by value in explicit
specializations of std::complex
for standard floating-point types. Since P1467R9 has
removed these explicit specializations, the corresponding constructor in the primary template that takes arguments
by const T&
are used instead. As a result, the following program becomes ill-formed after the changes.
#include <complex>
int main()
{
volatile double x = 0.0;
std::complex<double> z{x, x}; // ill-formed due to P1467R9 because const double& cannot be bound to a volatile double lvalue
}
Currently, libstdc++ has implemented complex specializations for extended floating-point types, but the corresponding constructors of these specializations takes two arguments by value, which is consistent with old specializations.
It seems that it's unintended to change the signatures of these constructors. Perhaps we should restore the signatures for required specializations.
Daniel:
Not only constructors are affected, but also all assignment operators taking the value_type
as
parameter and I suggest that LEWG should have a look at this issue.
[2023-05-20; Daniel comments and suggests wording]
The wording below attempts to restore the exact previous behaviour: For all floating-point types the
function parameter types are "by value" and for other types are "by const reference". The wording adds for
specification purposes a dependency to the concept std::floating_point
, but that doesn't mean that an
implementation couldn't realize the required effects without usage of concepts or the <type_traits>
header.
Note that we have already precedence for similar cases where we later reintroduced concept requirements to library
parts where no concept dependencies had exist before, such as the iterator_traits
specialization for
pointers (to object) or the additional constraints of set
's member function
iterator erase(const_iterator position)
or the constraint for reverse_iterator::operator->()
,
just to name a few.
One alternative approach could be to switch to "by-value" signatures only for the affected signatures. This could
affect user-defined floating-point-like types such as those with an arbitrary precision, therefore I started with
the most conservative approach restoring the original effects that was present in the
working draft N4910 and older ones. It might we worth pointing out that the existing "setter"
functions imag
and real
have always been using "by-value" signatures for all specializations.
There exists also the possible argument to close this issue as NAD based on the argument that all existing
non-member operators taking a value_type
argument had always been defined to use const T&
as parameter (such as the operator@(const T& lhs, const complex<T>& rhs)
forms).
My main argument to solve this issue as shown below is based on the ground that the refactoring done by
P1467R9 was mainly inspired to simplify the existing wording and to make it more easy to
integrate the addition of the extended floating-point types here, as quoted from
P1467R9 section 6.6. <complex>
:
[…] The explicit specializations of std::complex<T>
are removed. The only differences between
the explicit specializations was the explicit-ness of the constructors that take a complex number of a different type.
This issue has some overlap with LWG 3934, which suggests a yet missing specification for
the assignment operator taking the value_type
as parameter.
[2023-06-01; Reflector poll]
Set priority to 4 after reflector poll.
Several votes for NAD, as this only affects volatile
arguments,
so this might even be an accidental improvement.
Proposed resolution:
This wording is relative to N4950.
Modify 28.4.3 [complex], class template complex
synopsis, as indicated:
namespace std { template<class T> class complex { public: using value_type = T; constexpr complex(T re = T(), T im = T()) requires floating_point<T>; constexpr complex(const T& re = T(), const T& im = T()) requires (!floating_point<T>); […] constexpr complex& operator= (T) requires floating_point<T>; constexpr complex& operator= (const T&) requires (!floating_point<T>); constexpr complex& operator+=(T) requires floating_point<T>; constexpr complex& operator+=(const T&) requires (!floating_point<T>); constexpr complex& operator-=(T) requires floating_point<T>; constexpr complex& operator-=(const T&) requires (!floating_point<T>); constexpr complex& operator*=(T) requires floating_point<T>; constexpr complex& operator*=(const T&) requires (!floating_point<T>); constexpr complex& operator/=(T) requires floating_point<T>; constexpr complex& operator/=(const T&) requires (!floating_point<T>); […] }; }
Modify 28.4.4 [complex.members] as indicated:
constexpr complex(T re = T(), T im = T()) requires floating_point<T>; constexpr complex(const T& re = T(), const T& im = T()) requires (!floating_point<T>);-1- Postconditions:
real() == re && imag() == im
istrue
.
Modify 28.4.5 [complex.member.ops] as indicated:
[Drafting note: We have an pre-existing specification hole that the effects of the non-compound assignment operator taking the
value_type
as parameter are nowhere specified. This is going to be submitted as a separate issue, see LWG 3934.]
constexpr complex& operator+=(T rhs) requires floating_point<T>; constexpr complex& operator+=(const T& rhs) requires (!floating_point<T>);-1- Effects: Adds the scalar value
rhs
to the real part of the complex value*this
and stores the result in the real part of*this
, leaving the imaginary part unchanged.-2- Returns:
*this
.constexpr complex& operator-=(T rhs) requires floating_point<T>; constexpr complex& operator-=(const T& rhs) requires (!floating_point<T>);-3- Effects: Subtracts the scalar value
rhs
from the real part of the complex value*this
and stores the result in the real part of*this
, leaving the imaginary part unchanged.-4- Returns:
*this
.constexpr complex& operator*=(T rhs) requires floating_point<T>; constexpr complex& operator*=(const T& rhs) requires (!floating_point<T>);-5- Effects: Multiplies the scalar value
rhs
by the complex value*this
and stores the result in*this
.-6- Returns:
*this
.constexpr complex& operator/=(T rhs) requires floating_point<T>; constexpr complex& operator/=(const T& rhs) requires (!floating_point<T>);-7- Effects: Divides the scalar value
rhs
into the complex value*this
and stores the result in*this
.-8- Returns:
*this
.