5

For some home work I'm doing the following:

int main() {

    ofstream file("log.txt");

    file << setw(5)  <<  "i"
         << setw(15) <<  "h"
         << setw(15) <<  "n"
         << setw(15) <<  "sum"
         << setw(15) <<  "diff"
         << endl;

    auto write2file = [&file](int i, double h, double n, double sum, double diff) {
        file << setw(5)  << i
             << setw(15) << h
             << setw(15) << n
             << setw(15) << sum
             << setw(15) << diff
             << endl;
    };

    double a = 0;
    double b = 2;
    int n = 1;
    double h = (b-a)/n;
    double sum = sum_analytic;
    double diff = 1;

    while (diff > pow(10, -4)) {
        h = (b-a)/++n;
        sum = ntgrt(a, b, n, h);
        diff = abs(sum - sum_analytic);

        static int i = 0;
        write2file(++i, h, n, sum, diff);
    }
}

Considering the C++17 if init feature (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r1.html) I'm kind of seing the same pattern here when I declare h, sum and diff outside the while loop but they are used only inside.

Is there a better way to write it, may be taking advantage of C++17 features?

KcFnMi
  • 3,922
  • 8
  • 43
  • 90
  • Just curious: `n` seems to be a simple iteration variable but you did not mention that it is only used inside the loop. Do you need it elsewhere too? If not, you may have a look at the answers to [this](https://stackoverflow.com/questions/2687392/is-it-possible-to-declare-two-variables-of-different-types-in-a-for-loop) question. (Given that you choose Nicol Bolas' way and just use a `for` loop instead of the `while` loop.) – BlameTheBits May 27 '17 at 22:09

2 Answers2

21

Then what you want is a for loop:

for(double diff = 1; diff > pow(10, -4);)
{
    double h = (b-a)/++n;
    double sum = ntgrt(a, b, n, h);
    diff = abs(sum - sum_analytic);
    ....
}

There was no point in adding this capability to while since you can just use for and leave off the increment statement.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
  • 2
    I think this is a great answer. Nice way to keep the variable inside the loop scope as C++ Core Guidelines advice to do. Thanks. – Hitokage Feb 10 '18 at 08:05
  • 2
    `#define whileinit(init, test) for(init; (test);)` (not saying you _should_ do it) – Ayxan Haqverdili Feb 23 '20 at 13:23
  • @AyxanHaqverdili proof that there was a good reason to add that capability. – Nicolas Holthaus Oct 30 '20 at 18:56
  • “There was no point”  — I mean, the point is that the `for` loop needs to repeat the update expression, `while` wouldn’t. Consider `for (auto e = readdir(dir); e; e = readdir(dir)) …`. – Konrad Rudolph Jan 21 '21 at 22:36
  • @KonradRudolph: Doesn't `e = readdir(dir)` result in a reference to `e`? Which can be tested? So that's equivalent to `for(auto e = readdir(dir); e = readdir(dir);)`. How would the hypothetical `while` version be any different? It can't constantly recreate the `e` variable, after all. – Nicol Bolas Jan 21 '21 at 23:14
  • @NicolBolas The repetition still exists in your code (and stylistically I prefer the form where the update happens in the last part of the `for` loop header). A hypothetical `while` with initialisation analogous to P0305R1 would *not* have this repetition: `while (auto e = readdir(dir); e) …`. – Konrad Rudolph Jan 22 '21 at 09:05
  • @KonradRudolph: Are you saying that the initialization statement would be repeated on each cycle? That's not how initialization statements work in *any other context*. It would be very surprising. It would also make `e` constantly get recreated. – Nicol Bolas Jan 22 '21 at 14:24
  • @NicolBolas “It would also make e constantly get recreated.” — Sure, exactly like in a range-based `for` loop: `for (auto const x : range)`. I hope you agree that this *is* another context in which an initialisation is repeated on each cycle, i.e. contrary to what you said there’s precedent. – Konrad Rudolph Jan 22 '21 at 15:23
2

I'd start with some C++98 features to reduce repetition.

template <class I, class F>
void write2file(std::ostream &os, I i, F h, I n, F sum, F diff) {
    using std::setw;

    os  << setw(5) << i
        << setw(15) << h
        << setw(15) << n
        << setw(15) << sum
        << setw(15) << diff
        << "\n";
}

// ...
write2file(file, "i", "h", "n", "sum", "diff");


// ...
write2file(file, ++i, h, n, sum, diff);

It would appear that when they're used, i and n always have the same value. I'd just use one of the two rather than both.

Your pow(10, -4) would probably be better off as 1e-4.

I think some good use of C++98 can probably clean up the loop as well. I'd move it into a function template, something on this general order:

template <typename F>
void show_convergence(double a, double b, F f, double sum_a, std::ostream &os) {
    double diff = 1.0;

    for (int n=1; diff > 1e-4; n++) {
        double h = (b-a)/n;
        double sum = f(a, b, n, h);
        diff = abs(sum-sum_a);
        write2file(os, n, h, sum, diff);
    }
}

Technically, this wouldn't have to be a template--but it's fairly harmless, and avoids the ugliness of a pointer to a function (which can also lead to a fair degree of inefficiency). Then you'd call this something like this:

show_convergence(0, 2, ntgrt, sum_analytic, file);
Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • As an alternative to template, wouldn't it be interesting to do `auto write2file = [&](auto n, auto h, auto sum, auto diff){...`? – KcFnMi May 28 '17 at 01:44
  • 1
    You could, but I think it's big enough (and most of its content sufficiently unrelated to anything else) that it seems to me that it makes more sense to move it out on its own. – Jerry Coffin May 28 '17 at 01:51
  • 1
    @Shadow: Oops. Corrected. – Jerry Coffin May 28 '17 at 06:34