0

Not entirely a question, although just something I have been pondering on how to write such code more elegantly by style and at the same time fully making use of the new c++ standard etc. Here is the example

Returning Fibonacci sequence to a container upto N values (for those not mathematically inclined, this is just adding the previous two values with the first two values equal to 1. i.e. 1,1,2,3,5,8,13, ...)

example run from main:

std::vector<double> vec;
running_fibonacci_seq(vec,30000000);

1)

template <typename T, typename INT_TYPE>
    void running_fibonacci_seq(T& coll, const INT_TYPE& N)
    {
        coll.resize(N);
        coll[0] = 1;
        if (N>1) {
        coll[1] = 1;
        for (auto pos = coll.begin()+2;
            pos != coll.end();
            ++pos)
        {
            *pos = *(pos-1) + *(pos-2);
        }
        }
    }

2) the same but using rvalue && instead of & 1.e.

void running_fibonacci_seq(T&& coll, const INT_TYPE& N)

EDIT: as noticed by the users who commented below, the rvalue and lvalue play no role in timing - the speeds were actually the same for reasons discussed in the comments

results for N = 30,000,000

Time taken for &:919.053ms

Time taken for &&: 800.046ms

Firstly I know this really isn't a question as such, but which of these or which is best modern c++ code? with the rvalue reference (&&) it appears that move semantics are in place and no unnecessary copies are being made which makes a small improvement on time (important for me due to future real-time application development). some specific ''questions'' are

a) passing a container (which was vector in my example) to a function as a parameter is NOT an elegant solution on how rvalue should really be used. is this fact true? if so how would rvalue really show it's light in the above example?

b) coll.resize(N); call and the N=1 case, is there a way to avoid these calls so the user is given a simple interface to only use the function without creating size of vector dynamically. Can template metaprogramming be of use here so the vector is allocated with a particular size at compile time? (i.e. running_fibonacci_seq<30000000>) since the numbers can be large is there any need to use template metaprogramming if so can we use this (link) also

c) Is there an even more elegant method? I have a feeling std::transform function could be used by using lambdas e.g.

    void running_fibonacci_seq(T&& coll, const INT_TYPE& N)
    {
        coll.resize(N);
        coll[0] = 1;
        coll[1] = 1;
        std::transform (coll.begin()+2,
                coll.end(),         // source
                coll.begin(),       // destination
                [????](????) {      // lambda as function object
                    return ????????;
                });
    }

[1] http://cpptruths.blogspot.co.uk/2011/07/want-speed-use-constexpr-meta.html

woosah
  • 843
  • 11
  • 22
  • 3
    using move semantics, you could just construct a `std::vector` in the function and return it. `std::vector`'s move assignment operator will hand the `vector` over to the caller without copying it. – japreiss Jan 04 '13 at 18:32
  • 3
    No copy is made when passing the vector by reference either. In my opinion, there's nothing wrong with passing a container to a function by reference and having that function fill it for you. Are your timings consistently faster using the & signature vs. the && one? I feel that, somehow, the gain must be coming from elsewhere. – Aeluned Jan 04 '13 at 18:32
  • 6
    Nothing to do with your real question, but I cannot resist pointing out that `F(1476)` is the last Fibonacci number that's not infinity for standard IEEE-754 `double`s. – Daniel Fischer Jan 04 '13 at 18:33
  • 2
    _Want Speed? Pass by Value_ http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – K-ballo Jan 04 '13 at 18:54
  • Both of these pass by reference, the fact that one is an lvalue-reference and the other is an rvalue-reference means nothing in this case, within the function they are exactly the same. Your timing cannot be from this difference. Are you benchmarking properly? Several warm-up runs, randomized order, etc? – GManNickG Jan 04 '13 at 19:08
  • @GManNickG: There is no rvalue reference, `T` is deduced as `std::vector&` and `T&&` becomes `std::vector&` also. – Ben Voigt Jan 04 '13 at 19:25
  • @BenVoigt: Didn't see the template, so even more notably unimportant! :) – GManNickG Jan 04 '13 at 19:27
  • @DanielFischer You are right! should have used a more realistic example... – woosah Jan 04 '13 at 19:32
  • @GManNickGI should really go read what rvalues are first although not upto this in my cpp learning curve yet. I assumed it from the timing speed while doing a few examples out of the blue. – woosah Jan 04 '13 at 19:37
  • @GManNickG I did some real benchmarking by doing several warm-ups and in a randomized order like you said. You're right the difference didn't come between the & and && case, I don;t know what I was thinking, there is no temporary object in this case (?) I simply passed a value by reference anyway. – woosah Jan 04 '13 at 19:39

3 Answers3

2

Obvious answer:

std::vector<double> running_fibonacci_seq(uint32_t N);

Why ?

Because of const-ness:

std::vector<double> const result = running_fibonacci_seq(....);

Because of easier invariants:

void running_fibonacci_seq(std::vector<double>& t, uint32_t N) {
    // Oh, forgot to clear "t"!
    t.push_back(1);
    ...
}

But what of speed ?

There is an optimization called Return Value Optimization that allows the compiler to omit the copy (and build the result directly in the caller's variable) in a number of cases. It is specifically allowed by the C++ Standard even when the copy/move constructors have side effects.

So, why passing "out" parameters ?

  • you can only have one return value (sigh)
  • you may wish the reuse the allocated resources (here the memory buffer of t)
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • If the OP has a compiler with rvalue refs, RVO/NRVO won't offer much over them. – Puppy Jan 04 '13 at 19:10
  • @BenVoigt: You don't have to explicitly use rvalue references or std::move in order to access move support. – Nicol Bolas Jan 04 '13 at 19:30
  • @Nicol: The OP's code doesn't use move support either. Reference collapsing occurs. – Ben Voigt Jan 04 '13 at 19:31
  • @MatthieuM.: I know *your* code can move (or have construction elided). I was responding to DeadMG's assertion that your code is no better than the code in the question, because of compiler support for rvalue refs. – Ben Voigt Jan 05 '13 at 13:20
2

Due to "reference collapsing" this code does NOT use an rvalue reference, or move anything:

template <typename T, typename INT_TYPE>
void running_fibonacci_seq(T&& coll, const INT_TYPE& N);

running_fibonacci_seq(vec,30000000);

All of your questions (and the existing comments) become quite meaningless when you recognize this.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

Profile this:

#include <vector>
#include <cstddef>
#include <type_traits>

template <typename Container>
Container generate_fibbonacci_sequence(std::size_t N)
{
    Container coll;
    coll.resize(N);
    coll[0] = 1;
    if (N>1) {
      coll[1] = 1;
      for (auto pos = coll.begin()+2;
        pos != coll.end();
        ++pos)
      {
        *pos = *(pos-1) + *(pos-2);
      }
    }
    return coll;
}

struct fibbo_maker {
  std::size_t N;
  fibbo_maker(std::size_t n):N(n) {}
  template<typename Container>
  operator Container() const {
    typedef typename std::remove_reference<Container>::type NRContainer;
    typedef typename std::decay<NRContainer>::type VContainer;
    return generate_fibbonacci_sequence<VContainer>(N);
  }
};

fibbo_maker make_fibbonacci_sequence( std::size_t N ) {
  return fibbo_maker(N);
}

int main() {
  std::vector<double> tmp = make_fibbonacci_sequence(30000000);
}

the fibbo_maker stuff is just me being clever. But it lets me deduce the type of fibbo sequence you want without you having to repeat it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524