Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

adding data to stl container without raw loops

Tags:

c++

c++11

stl

I've often seen that you can replace all handwritten/raw loops with stl algorithms. Just to improve my C++ knowledge I've been trying just that.

To populate a std::vector with data I use a for loop and the loops index.

unsigned int buffer_size = (format.getBytesPerSecond() * playlen) / 1000;

    // pcm data stored in a 'short type' vector
    vector<short> pcm_data;

    for (unsigned int i = 0; i < buffer_size; ++i)
    {
        pcm_data.push_back( static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)) );
    }

The above code works fine, as you can see I use the for loops index 'i' for the algorithm to be correct.

How can someone replace that for loop with something from the standard?

The only functions i've seen that almost allow me to do it are std::transform and std::generate, but both of those wouldn't work because I require an index value to increment for the code.

EG:

generate_n(begin(pcm_data), buffer_size, [] ()
    {
        return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i??
    });

    transform(begin(pcm_data), end(pcm_data), begin(pcm_data) [] (???)
    {
        return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i??
    });

Or am I simply going too far into the idea of "no raw loops"?

like image 420
S Grey Avatar asked Oct 14 '14 10:10

S Grey


1 Answers

The real solution here would be to define an appropriate iterator, something like:

class PcmIter : public std::iterator<std::forward_iterator_tag, short>
{
    int myIndex;
    double myAmplitude;
    double myFrequency;
    short myValue;

    void calculate()
    {
        myValue = myAmplitude * std::sin( 2 * M_PI * myIndex * frequency );
    }
public:
    PcmIter( int index, amplitude = 0.0, frequency = 0.0 )
        : myIndex( index )
        , myAmplitude( amplitude )
        , myFrequency( frequency )
    {
        calculate();
    }

    bool operator==( PcmIter const& other ) const
    {
        return myIndex == other.myIndex;
    }
    bool operator!=( PcmIter const& other ) const
    {
        return myIndex != other.myIndex;
    }
    const short& operator*() const
    {
        return myValue;
    }

    PcmIter& operator++()
    {
        ++ myIndex;
        calculate();
    }

    PcmIter operator++( int )
    {
        PcmIter results( *this );
        operator++();
        return results;
    }
};

In practice, I suspect that you could get by with having operator* return a value, which you calculate at that point, and not having a myValue member.

To use:

std::vector<short> pcmData(
    PcmIter( 0, amplitude, frequency),
    PcmIter( buffer_size ) );

(The amplitude and the frequency are irrelevant for the end iterator, since it will never be dereferenced.)

Ideally, this would be a random_access_iterator, so that the constructor to vector will calculate the number of elements, and pre-allocate them. This involves implementing a lot more functions, however.

If you're courageous, and have to do similar things a lot, you could consider making the iterator a template, to be instantiated over the function you're interested in.

And while I've not had a chance to play with them lately, if you're using Boost, you might consider chaining a transform_iterator and a counting_iterator. It's still a bit wordy, but the people who did the iterators at Boost did the best they could, given the somewhat broken design of STL iterators.

like image 117
James Kanze Avatar answered Sep 20 '22 13:09

James Kanze