Thanks,
Daniel Herring
dnun...@gmail.com
Over the past few years LWG Issue #387 has dealt with two major issues.
In its earliest form, it improved C++ access to the real and imaginary
components of std::complex. In its current form, this issue mostly
addresses the data layout required for binary compatibility with C,
leaving a somewhat messy C++ interface.
C compatibility requires a strict layout of the std::complex class; the
current wording specifies that complex<T> must be reinterpret_cast
equivalent to T[2]. This neatly solves the C compatibility issue.
However, it does not fully address the C++ interface, nor does it
provide a testable condition for users to ensure that their compiler
fully supports the new API.
The current version of the issue [link 1] presents a "proposed API":
std::complex<T> z; reinterpret_cast<cv T(&)[2]>(z);
T real() const;
void real(T val);
T imag() const;
void imag(T val);
Another version of the issue [link 2] presented an older API:
T & real();
T & imag();
This return-by-reference has a distinct advantage over the set-by-value:
temporaries can be avoided.
e.g.
complex<float> z;
float tmp;
get_something(tmp);
z.real(tmp);
versus
complex<float> z;
get_something(z.real());
The proposed API leaves two situations which force user code to use
reinterpret_cast. The first is to pass an individual component by
reference and the second is to pass the complex as an array.
e.g.
T & lreal(complex<T> &z) { return reinterpret_cast<T(&)[2]>(z); }
and
extern "C" void f(T (&z)[2]);
f(reinterpret_cast<T(&)[2]>(z));
These situations frequently appear in code for language bindings, I/O,
byte-swapping, and hardware vectorization.
As for testability, it is important that the end-user be able to
implement automated tests (a la autoconf) to determine whether a
compiler supports a new feature. The return-by-reference presented in
the older API was easily testable; "z.real()=5" only compiles on
platforms which support it. Part of the new API is also testable;
"z.real(5)" will only compile on supporting platforms. However the
reinterpret_cast, by its nature, is not directly testable. Code
containing reinterpret_cast will compile on any C++ system; and a
sophisticated test suite would be required to run such code using magic
numbers and several data types just to be sure that the data appears to
be packed properly.
These two uses provide justification for adding the following to the
proposed C++ API.
typedef T (&array_type)[2]; // unable declare directly
in function prototype
array_type & c_array(); // similar to std::string::c_str()
const array_type & c_array() const ;
T & lreal(); // "l" for lvalue
const T & lreal() const;
T & limag();
const T & limag() const;
The names may need improvement, and return-by-reference may supersede
the getters and setters of the proposed API, but these basic methods are
required for C++ users to safely use the layout guarantee that Issue
#387 provides for compatibility with C and other languages. If they are
not implemented in the standard, then they will appear in user libraries.
Note that it is not sufficient to provide
T * c_array();
Since this discards type information which may be useful for template
expansions.
[link 1] July 22, 2008 --
http://open-std.org/JTC1/SC22/WG21/docs/lwg-defects.html#387
[link 2] (now broken) Feb 15, 2008 --
http://open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#387
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
I don't like the name of c_array(). I believe it's meant to mimic the
string::c_str() method, but the arguments that lead to the choice of the
name c_str() (namely: it returns a "C string" = null-terminated) do not
apply here. Maybe "as_array" is a more apt name. The as_xxx naming
conversion is used in several place in Boost and makes more sense, IMHO.
Moreover, I would prefer having real() and imag() return references
rather than having lreal() and limag(), although I understand that it
might change the ABI.
>
> Note that it is not sufficient to provide
> T * c_array();
> Since this discards type information which may be useful for template
> expansions.
>
That's correct and it's no problem as T[2] decays to T*, so the proposed
signature is enough to cover both cases.
Notice that we still need to cope with the requirement that an array of
std::complex<> should be equivalent as an array of T[2], but that's not
something you would find in the interface anyway.
Ganesh
My question is, why not just make it a struct -- i.e. make the
data members public? The discussion of issue #387 clearly indicates
that a complex number is "just a pair of numbers with no particular
invariant to maintain." It also implies that any representation but
Cartesian coordinates is not justified, and addresses the exactly
same layout as T[2]. Then I see no point in using member functions
to "mimic" any encapsulation that doesn't/needn't exist.
public:
T data[2];
T& real() { return data[0]; }
const T& real() const { return data[0]; }
T& imag() { return data[1]; }
const T& imag() const { return data[1]; }
Or, even without making the data members public, I think it's good to
specify that the internal representation be T data_[2]; and the public
interface be specified in terms of the array. (See below for the code.)
> The current version of the issue [link 1] presents a "proposed API":
> std::complex<T> z; reinterpret_cast<cv T(&)[2]>(z);
Better than none, but reinterpret_cast is really ugly and only a hack.
Presenting reinterpret_cast as the official solution risks undermining
the general wisdom that things as ugly as reinterpret_cast should be
used rarely, carefully, and only in low-level contexts, and making
even not-so-expert programmers get used to reinterpret_cast.
> T & lreal(complex<T> &z) { return reinterpret_cast<T(&)[2]>(z); }
This does not compile. (T(&)[2] cannot be converted to T&.)
It probably should be:
T & lreal(complex<T> &z) { return reinterpret_cast<T(&)[2]>(z)[0]; }
(Not that I endorse reinterpret_cast...)
> These two uses provide justification for adding the following to the
> proposed C++ API.
> typedef T (&array_type)[2]; // unable declare directly
> in function prototype
> array_type & c_array(); // similar to std::string::c_str()
> const array_type & c_array() const ;
The name "(c_)array" looks too general and maybe "pair" is better for
the type name, but I'm not so sure about that. And wouldn't it be nice
to follow the convention of naming "data()" the member function that
exposes the internal structure?
And array_type is already a reference (to an array of non-const T's);
isn't (const) array_type& a double reference?
public:
typedef T (& pair)[2];
typedef const T (&const_pair)[2];
pair data() { return data_; }
const_pair data() const { return data_; }
private:
T data_[2];
> T & lreal(); // "l" for lvalue
> const T & lreal() const;
> T & limag();
> const T & limag() const;
I'm not sure if we really need separate lvalue versions, because the
current standard has real() and imag() only as rvalues and the lvalue
versions can be used where the rvalue versions are expected.
--
Seungbeom Kim
[...]
| Moreover, I would prefer having real() and imag() return references
| rather than having lreal() and limag(), although I understand that it
| might change the ABI.
It is an ABI change that some widespread compilers have already made
though -- based on earlier `Ready' resolution of the issue.
--
Dr. Gabriel Dos Reis (g...@cs.tamu.edu), Assistant Professor
http://www.cs.tamu.edu/people/faculty/gdr
Texas A&M University -- Department of Computer Science
301, Bright Building -- College Station, TX 77843-3112