/*
=================================================================
Step 1
=================================================================
*/
#include "process_values.h"
#if !defined(TEST_AVERAGER)\
&& !defined(TEST_MINMAXER)
#include "Averager.h"
#include <cstdlib>
// EXIT_FAILURE (macro)
// EXIT_SUCCESS (macro)
#include <iostream>
// std::cout
// std::flush
int main(int const ac, char const** av) {
switch((ac == 1) ? ' ' : *av[1]) {
default:
std::cout << "Usage: " << av[0] << " a # (no args)"
<< std::endl;
return EXIT_FAILURE;
case ' ':
my::process_values<my::Averager<float>>();
// my::process_values<my::MinMaxer<float>>();
break;
}
return EXIT_SUCCESS;
}
#else
#include "pxt.h"
// PX (macro) + overloading magic for TDD)
#include <sstream>
// std::istringstream
// std::ostringstream
#ifndef TEST_AVERAGER // :::::::::::::::::::::::::::::::::::::::::
void testing_averager() {} // avoids to change the main program
#else
#include "Averager.h"
auto do_averages = [](auto& _1, auto& _2) {
return my::process_values<my::Averager<float>>(_1, _2);
};
void typical_good_input() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"1.5 7" "\n"
"15.7" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"4.25" "\n"
"15.7" "\n";
} ////////////////////////////////////////////////////////////
}
void some_empty_inputs() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{"\n"};
std::ostringstream output{};
PX(do_averages(input, output), output.str()) == "";
} ////////////////////////////////////////////////////////////
{ ////////////////////////////////////////////////////////////
std::istringstream input{" \n"};
std::ostringstream output{};
PX(do_averages(input, output), output.str()) == "";
} ////////////////////////////////////////////////////////////
}
void some_invalid_inputs() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"1 S 7" "\n"
"15.7" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"non-numeric input - line ignored" "\n"
"15.7" "\n";
} ////////////////////////////////////////////////////////////
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"xxx 7" "\n"
"1 xyz 7" "\n"
"1 xyz" "\n"
"1 abc" "\n" // <--- will set, both FAIL and EOF,
// for some older C++ Libraries;
// see fix in `process_values()`
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n";
} ////////////////////////////////////////////////////////////
}
void testing_averager() {
typical_good_input();
some_empty_inputs();
some_invalid_inputs();
}
#endif // TEST_AVERAGER
#ifndef TEST_MINMAXER // :::::::::::::::::::::::::::::::::::::::::
void testing_minmaxer() {} // avoids to change the main program
#else
#include <cassert> // <-- consider to remove when some
// tests are actually implemented
void testing_minmaxer() {
//
assert(!"TBD – specific MinMaxer tests need to be added");
//
}
#endif // TEST_MINMAXER
int main() {
testing_averager();
testing_minmaxer();
}
#endif // (automated tests)
# STEP 1
**IMPORTANT NOTE:**\
The *Interactive Version* of this program wants to read input from
the terminal. So please do not be surprised once you compile and
run it nothing seems to happen:
- **The program probably just waits for some keyboard input.**
- So just type some numbers, all in the same line.
- The response then should be the average of these.
- You can enter as many input lines as you want.
- **To terminate enter an empty line.**
- To compile and run the *Interactive Version*
- at the command line (eg. in VSCode) type the following:
```bash
g++ -std=c++17 *.cpp && ./a.out`;
```
- for the **onlineGDB** version just click the *RUN* button.
- You may also run builtin tests as suggested when working in
*TDD* style (= Test Driven Driven Development).
- The command line now should look like this:
```bash
gcc -DTEST_AVERAGES -std=c++17 *.cpp && ./a.out;
```
- When using **onlineGDB** find its configuration menu (the
cog-wheel top-right) and add the below as *EXTRA* compile
option:
```bash
-DTEST_AVERAGER
```
When the program indicates it passes all the tests you have two
basic options to proceed:
- **Option A** is to do a thorough examination of the code **so
that can ask informed questions when the trainer later walks you
through the code. Be sure to mark **every language construct**
(and maybe other aspects) you do not understand and for which
you want an explanation later.\
Proceed through the files in the following order, giving the
most attention to the first three, between you may need to skip
back and forth a bit:
- `process_values.h`
- `IProcessor.h`
- `Averager.h`
- `main.cpp` (limit your analysis to line 16 … 19)
- `pxt.h` and `pxt.cpp` may be skipped for now as they mostly
supply the "magic" of the TDD-part used by the test code in
the remaining `main` program. **You shouldn't give it much
attention right now, if any at all.**
- **Option B** is to analyze only as much as is necessary to
understand how the class `Averager` supplies the calculation of
the per line averages. Then extend the program so that it can be
compiled with a class `MinMaxer` which instead of calculating
averages finds and outputs the minimum and maximum af all the
values in a line. Here is how you may proceed:
- Copy and and rename the files
- `Averager.h` -> `Minmaxer.h`
- `Averager.cpp` -> `Minmaxer.cpp`
- In the *MinMaxer* copies do the obvious renaming (do not
forget the include guards in the header).
- Replace the single data member `sum_` of `class MinMaxer` with
two members `min_` an `max_`.
- Optionally change the data member
- `int count_{0}` to
- `bool first_{true}`.
- Adapt the counting logic to properly recognize whether the
value is supplied (which must then be assigned to both, `min_`
and `max_`) or a subsequent value (which must be assigned to
`min_` resp. `max`, if it is smaller or larger that the
current one.
- The necessary modification for testing already exists in
`main.cpp`:
- it is suggested you start with the interactive version by
just moving the `//`-comment one line up;
- then you may proceed by adding TDD-style testing in the
in the already prepared section as indicated by\
`assert(!"TBD – specific MinMaxer tests need to be added");`
**Note:**\
The easiest way to create a set of tests for the `MinMaxer` class
is to simply copy down the tests for the`Averager` and adapt the
expectations. OTH, as nothing changed wrt. the logic to recognize
valid input, it doesn't seem to make that much sense to keep the
tests with bad input. Instead, maybe change the the templated data
type and test the `MinMaxer` class eg. with a signed or unsigned
integral type.
------------------------------------------------------------------
TODO (optional - meant for further self-study only):
- Implement other kinds of *Value Processors* eg. one to calculate
a percentage by which the average is placed (at a linear scale)
between minimum and maximum, like that:
```txt
min_ >|----+---------------------|< max_ ---
- - - |----^ offset (= % relative to spread)
- - - |<-- spread -------------->| - - - - -
```
- You may choose to write the `Percentager` class from scratch,
as its logic straight forward.
- Alternatively you may create it by "re-using" the `Averager`
and the `Minmaxer`, though that requires a bit of change to
both:
- either you supply the getters which are necessary so that
the `Percentager` can do its work;
- alternatively you could increase the visibility of their
data members to `protected` and re-use both together as base
classes for the `Percentager`.
**Note:**\
For the interactive version of `main` you may apply the following
modification to control which *Value Processor* to use via a
single letter command line argument:
```cpp
… // +-- now checking for ONE(!) argument present
// |
switch((ac == 2) ? ' ' : *av[1]) {
default:
std::cout << "Usage: " << av[0] << " a # average\n"
" -or- " << av[0] << " x # extrema\n"
" -or- " << av[0] << " p # percent\n"
<< std::flush;
return EXIT_FAILURE;
case 'a':
my::process_values<my::Averager<float>>();
break;
case 'x':
my::process_values<my::MinMaxer<float>>();
break;
case 'p':
my::process_values<my::Percentager<float>>();
break;
}
…
```
#ifndef MY_PROCESS_VALUES_H_
#define MY_PROCESS_VALUES_H_
#include "IProcessor.h"
#include <iostream>
#include <sstream>
namespace {
template<typename Processor>
void process_single_line(std::string const& line, Processor& processor) {
std::istringstream iss{line};
typename Processor::value_type value{};
while (iss >> value) {
processor += value;
}
bool const received_some_values_but_not_seen_eol{
processor && not iss.eof()
};
bool const fail_bit_is_set_but_eof_bit_is_not{
iss.fail() && !iss.eof()
};
if (received_some_values_but_not_seen_eol
or fail_bit_is_set_but_eof_bit_is_not
) {
throw "non-numeric input";
}
}
} // namespace
namespace my {
template<typename Processor>
void process_values(std::istream& in, std::ostream& out) {
std::string line{};
while (std::getline(in, line)) {
Processor processor;
line.append(1, ' '); // get around EOF bug on
// some old C++ libraries
try {
process_single_line(line, processor);
}
catch (char const* errmsg) {
out << errmsg << " - line ignored" << std::endl;
continue;
}
if (not processor) // empty line (=> end loop)
return;
out << processor << std::endl; // show result
}
}
template<typename Processor>
void process_values() {
process_values<Processor>(std::cin, std::cout);
}
} // namespace
#endif // include guard
#include "process_values.h"
// for compile test only
#ifndef MY_I_PROCESSOR_H_
#define MY_I_PROCESSOR_H_
#include <iosfwd>
#include <type_traits>
namespace my {
template<typename T>
constexpr auto all_required_operations() {
T a, b;
return noexcept(a = b)
&& noexcept(a += b)
&& noexcept(a + b)
&& noexcept(a < b)
&& noexcept(a / int{});
}
template<typename T>
struct IProcessor {
static_assert(std::is_arithmetic_v<T>,
"designed for arithmetic types only");
using value_type = T;
// @throws nothing (but still may crash the whole process if
// an implementing class does not adhere to its own `noexcept`
// specification)
//
virtual void operator+=(value_type rhs)
/*noexcept(all_required_operations<value_type>())*/ = 0;
// @post `true` returned if there has been at least ONE prior
// call to `operator++`
//
virtual explicit operator bool() const =0;
// @throws any exception an `std::ostream` may throw for any
// reason when the stream insertion operation (`operator<<`)
// for something of type `value` throws
//
// @pre there must have been one prior call to `operator+=`
//
// @pre the `std::ostream` argument must refer to an ostream
// in the "good" state (none of EOF-, FAIL-, or BAD-bit set)
//
// @post the ostream ostream may not any longer be in the good
// state but unless it has been set to notify error condition
// via an exception none of its format settings differs from
// what it had been when the operation started
//
virtual void print(std::ostream&) const =0;
};
template<typename T>
inline
decltype(auto) operator<<(std::ostream& lhs,
IProcessor<T> const& rhs) {
rhs.print(lhs);
return lhs;
}
} // namespace
#endif // include guard
#include "IProcessor.h"
// for compile test only
// NOTE:
// to show effectivity of `static_assert` enable the following:
// class fails_the_is_arithmetic_test : my::IProcessor<void> {};
#ifndef MY_AVERAGER_H_
#define MY_AVERAGER_H_
#include "IProcessor.h"
#include <cassert>
#include <iostream>
namespace my {
template <typename T>
class Averager : public IProcessor<T> {
public:
// normally constructible and destructible
Averager() =default;
~Averager() =default;
// neither copyable nor movable (as not needed)
Averager(Averager const&) =delete;
Averager(Averager&&) =delete;
Averager& operator=(Averager const&) =delete;
Averager& operator=(Averager&&) =delete;
using value_type = T; // (mostly for readability)
private:
value_type sum_{};
int count_{0};
public:
void operator+=(value_type rhs)
noexcept(noexcept(rhs = rhs)
&& noexcept(rhs < rhs)
&& noexcept(rhs + rhs)
&& noexcept(rhs += rhs)) override {
sum_ += rhs;
++count_;
}
explicit operator bool() const override {
return (count_ > 0);
}
void print(std::ostream& os) const override;
};
template<typename T>
void Averager<T>::print(std::ostream& os) const {
assert(count_ > 0);
auto const average = sum_ / count_;
os << average;
}
} // namespace
#endif // include guard
#include "Averager.h"
// for compile test only
#ifndef PXT_off
#ifndef PXT_ostream
#define PXT_ostream std::cout
#endif
#define PX(...)\
PXT_namespace::PXT_class{__func__, __FILE__, __LINE__, #__VA_ARGS__, (__VA_ARGS__)}
#if defined(PXT_HAVE_BOOST_TYPEINDEX)\
|| defined(__has_include) && __has_include(<boost/type_index.hpp>)
#include <boost/type_index.hpp>
#define PT(...)\
PXT_namespace::PXT_helper(__func__, __FILE__, __LINE__, #__VA_ARGS__,\
boost::typeindex::type_id_with_cvr<__VA_ARGS__>())
#endif
#if !defined(PX)\
|| !defined(PT)
struct PXT_dummy{
template<typename T> void operator==(T const&) const {}
template<typename T> void operator*=(T const&) const {}
template<typename T> void operator>>=(T const&) const {}
template<typename T> void operator%=(T const&) const {}
};
#if !defined(PX)
#define PX(...) PXT_dummy{}
#endif
#if !defined(PT)
#define PT(...) PXT_dummy{}
#endif
#endif
#include <sstream>
#include <iostream>
namespace PXT_namespace {
class PXT_class {
bool done_{};
char const* func_{};
char const* file_{};
int const line_{};
char const* texpr_{};
std::stringstream streamed_result{};
std::stringstream expected_result{};
std::ostream& source_location() const;
void plain_output();
void two_line_compare();
void multi_line_compare();
void glob_pattern_compare();
struct Result {
unsigned long long total;
unsigned long long failed;
~Result();
};
static Result counter;
public:
static std::ostream& out_stream;
static bool ignore_expectations;
template<typename T>
PXT_class(char const* func, char const* file, int line, char const* texpr, T const& expr)
: func_(func), file_(file), line_(line), texpr_(texpr)
{
streamed_result.copyfmt(out_stream);
streamed_result << expr;
}
~PXT_class() {
if (not done_) plain_output();
}
template<typename T>
void operator==(T const& expr) {
expected_result.copyfmt(streamed_result);
expected_result << expr;
two_line_compare();
}
void operator>>=(std::string lines) {
expected_result << lines;
multi_line_compare();
}
void operator*=(std::string smatch) {
glob_pattern_compare();
}
};
} // namespace
#ifdef PXT_header_only
#include "pxt.cpp"
#endif
#endif
#ifndef PXT_header_only
#include "pxt.h"
#endif
#include <iostream>
namespace PXT_namespace {
std::ostream& PXT_class::out_stream{PXT_ostream};
PXT_class::Result PXT_class::counter{0, 0};
PXT_class::Result::~Result() {
if (failed > 0) {
PXT_class::out_stream << "** " << failed << " of "
<< total << " tests failed **" << std::endl;
}
else if (total > 0) {
PXT_class::out_stream << "** ALL " << total << " TESTS PASSED **" << std::endl;
}
}
std::ostream& PXT_class::source_location() const {
return out_stream << file_ << ' ' << '[' << func_ << ':' << line_ << ']' << ' ' << texpr_;
}
void PXT_class::plain_output() {
source_location() << " == " << streamed_result.str() << std::endl;
}
void PXT_class::two_line_compare() {
done_ = true;
++counter.total;
if (streamed_result.str() != expected_result.str()) {
++counter.failed;
source_location() << '\n'
<< " = received: " << streamed_result.str() << '\n'
<< " ! expected: " << expected_result.str() << std::endl;
}
}
void PXT_class::multi_line_compare() {
std::string streamed_line{};
std::string expected_line{};
std::ostringstream common{};
while (std::getline(streamed_result, streamed_line),
(expected_result >> std::ws),
std::getline(expected_result, expected_line),
!streamed_result.eof()
&& !expected_result.eof()
&& (streamed_line == expected_line)) {
common << " = " << expected_line << '\n';
}
done_ = true;
++counter.total;
if (streamed_result.eof()
&& expected_result.eof()) return; // no difference
++counter.failed;
if (streamed_result.eof()) { // less streamed than expected
source_location() << '\n'
<< common.str()
<< " + expected: " << expected_line << std::endl;
return;
}
if (expected_result.eof()) { // less expected than streamed
source_location() << '\n'
<< common.str()
<< " + received: " << streamed_line << std::endl;
return;
}
source_location() << '\n' // difference in streamed to expected
<< common.str()
<< " ~ received: " << streamed_line << '\n'
<< " ~ expected: " << expected_line << std::endl;
}
} // namespace