Type-punning a std::array

This is a discussion on Type-punning a std::array within the C++ Programming forums, part of the General Programming Boards category; I have the need to treat a std::array<float>, which is of even length N, as if it was a std::array<complex> ...

  1. #1
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,274

    Type-punning a std::array

    I have the need to treat a std::array<float>, which is of even length N, as if it was a std::array<complex> of length N / 2. If it was a bare array, I could just type-cast it like this:

    Code:
    float real[N];
    complex *pComplex = reinterpret_cast<complex *>(real);
    Is there ANY way to accomplish this with std::array? As of yet, the only way I can see is to make a new array and copy the elements over to it. But this is a common trick when implementing fast Fourier transforms, and the code base uses std::array prolifically.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  2. #2
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    This seems to work, but maybe it's not what you want.
    Code:
    #include <iostream>
    #include <array>
    #include <complex>
    using std::cout;
    
    typedef std::array<float,10> FA;
    typedef std::array<std::complex<float>,5> CA;
    
    int main() {
        FA fa;
        CA* pca;
        pca = reinterpret_cast<CA*>(&fa);
        for (int i = 0; i < 10; i++)
            fa[i] = i;
        cout<< pca->size() <<'\n';
        for (int i = 0; i < 5; i++)
            cout << pca->operator[](i).real() << ','
                 << pca->operator[](i).imag() << '\n';
        return 0;
    }
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  3. #3
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,274
    Quote Originally Posted by oogabooga View Post
    This seems to work, but maybe it's not what you want.
    I'm not entirely surprised that works, at least on one compiler. But I would never get away with anything such as that in this code...

    Also, I forgot to mention that the obvious solution of taking &foo[0] and casting it to the other kind of pointer, is not possible because we only access the arrays via iterators.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,415
    O_o

    What oogabooga said has a strong shot of working and being portable.

    It depends on the library, but so long as the array is the only member variable of `std::array' and `std::complex' is similarly clean, and there is no logical reason it should be otherwise, the cast is perfectly fine. It is technically not covered by the standard but because of a lot of unrelated guarantees you should be fine.

    Flaky code is bad code so you should add a `static_assert' line checking `sizeof' against `sizeof' so you may fail gracefully when they aren't the same.

    *shrug*

    My advice however: you should add two wrapper functions (handled via "RAII") that use meta-programming to conditionally cast the result when the sizes are appropriate or clone the values for libraries where that isn't the case recopying the modified data back and deleting the allocated `std::array' instance when the handle would have gone out of scope. In this way you can have your performance where possible. This does however require more reasoning about the client code because you wouldn't get "instant" mutual updates. (That too can be handled at a cost.)

    Soma

  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,415
    Also, I forgot to mention that the obvious solution of taking &foo[0] and casting it to the other kind of pointer, is not possible because we only access the arrays via iterators.
    Ah, I may have solved your problem if, and only if, you are using template interfaces where these iterators are used or may update those same interfaces to use iterators of a different kind.

    In both cases you will use the virtual iterator pattern to solve your problem.

    In the first case the compiler will inline most transformation resulting in no performance loss.

    The second case would require traditional virtual functions and a new, but small, class hierarchy to masquerade as both types of iterators.

    [Edit]
    I'll include, I hope, a little more information in a private message.
    [/Edit]

    Soma
    Last edited by phantomotap; 04-23-2012 at 08:09 PM.

  6. #6
    Registered User gardhr's Avatar
    Join Date
    Apr 2011
    Posts
    151
    Quote Originally Posted by brewbuck View Post
    Is there ANY way to accomplish this with std::array?
    Most likely, but it's certainly not guaranteed. First, there is the alignment issue. As long as we're talking about complex<POD>, however, that shouldn't be a problem. The next question is whether or not your library source indeed defines the real and imaginary components consecutively (in that order). Still very likely, but runtime checks should be made to be sure. Incidentally, all of that can be done very efficiently using static variables:

    Code:
    #include <complex>
    template <typename Real>
    bool okay_to_cast_std_complex_to_array(void)
    {
    	static Real zero = 0, one = 1;
    	static std::complex<Real> test(zero, one);
    	static bool result = (sizeof(test) == sizeof(Real) + sizeof(Real)) && ((Real*)&test)[0] == zero;
    	return result;
    }
    (Note:The above cast is aligned-access-safe, by the way, because of the short circuit with the sizeof() test.)

    Just place within an assert() statement somewhere in your code and your done.

  7. #7
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,308
    A pointer can itself be treated as an iterator in almost every way. The only problem that comes to mind would be if you expect to be calling .begin() and .end() on them.

    How about...
    Code:
    #include <complex>
    
    class wrapper {
    	std::complex<double> *a;
    	size_t n;
    public:
    	wrapper(double *a, size_t n) :
    		a(reinterpret_cast< std::complex<double>* >(a)),
    		n(n * sizeof(double) / sizeof(std::complex<double>))
    	{}
    	const std::complex<double> *begin() const { return a; }
    	const std::complex<double> *end() const { return &a[n]; }
    	std::complex<double> *begin() { return a; }
    	std::complex<double> *end() { return &a[n]; }
    };
    
    
    int main()
    {	double foo[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
    	wrapper w(&foo[0], sizeof(foo)/sizeof(foo[0]));
    	for (std::complex<double> *it = w.begin(); it != w.end(); ++it)
    	{
    		std::cout << *it << std::endl;
    	}
    }
    If you've seen any of the recent presentations about the new C++ standard, you may have seen that the non-member version of begin and end are now also the preferred thing to use if you have a new enough compiler.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  8. #8
    Registered User gardhr's Avatar
    Join Date
    Apr 2011
    Posts
    151
    Quote Originally Posted by iMalc View Post
    A pointer can itself be treated as an iterator in almost every way. The only problem that comes to mind would be if you expect to be calling .begin() and .end() on them.

    How about...
    Code:
    #include <complex>
    
    class wrapper {
    	std::complex<double> *a;
    	size_t n;
    public:
    	wrapper(double *a, size_t n) :
    		a(reinterpret_cast< std::complex<double>* >(a)),
    		n(n * sizeof(double) / sizeof(std::complex<double>))
    	{}
    	const std::complex<double> *begin() const { return a; }
    	const std::complex<double> *end() const { return &a[n]; }
    	std::complex<double> *begin() { return a; }
    	std::complex<double> *end() { return &a[n]; }
    };
    
    
    int main()
    {	double foo[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
    	wrapper w(&foo[0], sizeof(foo)/sizeof(foo[0]));
    	for (std::complex<double> *it = w.begin(); it != w.end(); ++it)
    	{
    		std::cout << *it << std::endl;
    	}
    }
    If you've seen any of the recent presentations about the new C++ standard, you may have seen that the non-member version of begin and end are now also the preferred thing to use if you have a new enough compiler.
    Actually, you should be able to pun a native array directly to an std::array and vice versa.

    Code:
    int main(void)
    {
    	assert(okay_to_cast_std_complex_to_array<float>());
    	const std::size_t size = 1024;
    	typedef std::array<std::complex<float>, size> sascf;
    	typedef std::complex<float> acf[size];
    	//...
    	acf buffer;
    	sascf& s = (sascf&)buffer;
    	s.begin()->real(sqrt(2.0));
    	acf& a = (acf&)s;
    	std::cout << a[0].real() << std::endl;
    }
    Last edited by gardhr; 04-24-2012 at 11:08 AM.

  9. #9
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,274
    Some good ideas. Thanks.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. array type flexible array?
    By AaronP in forum C Programming
    Replies: 12
    Last Post: 12-12-2011, 09:21 PM
  2. Replies: 2
    Last Post: 05-23-2011, 03:04 PM
  3. array type has incomplete element type
    By philgrek in forum C Programming
    Replies: 29
    Last Post: 05-06-2011, 03:42 PM
  4. error: array type has incomplete element type
    By gerger in forum C Programming
    Replies: 8
    Last Post: 10-05-2010, 08:40 AM
  5. What type is a 2D array?
    By King Mir in forum C Programming
    Replies: 15
    Last Post: 05-18-2006, 01:37 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21