Class template for the encapsulation of datasheet specifications using optionals
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
7
down vote
favorite
A couple of years ago I wrote a pair of class templates to encapsulate specifications which have one or more of a minimum, typical, and/or maximum value, e.g. on a datasheet like this one for the 741 op amp:
That code required me to re-invent the wheel a bit since I was using an old compiler, and I had not discovered boost::optional
/std::optional
yet. I've re-written that code to use a single class template called Spec
, C++11 and C++14 features, and boost::optional
or std::optional
. I've also included some additional utilities (inserting a Spec
into a std::ostream
, clamping Spec
values, computing spec limits based on a value and a % tolerance, etc.). I have not added yet support for guardbands as in the previous implementation, but I plan to do so later.
Here is the implementation using boost::optional
. My compiler is Visual Studio 2017 (which can use C++17's std::optional
), but I'm only using C++14 features in the project this will be used in. I've provided commented-out code below to use std::optional
instead:
#ifndef SPEC_H
#define SPEC_H
#include <ostream>
#include <stdexcept>
// To use std::optional, if supported by compiler
//#include <optional>
// To use boost::optional
#include <boost/none.hpp> // boost::none_t
#include <boost/optional.hpp>
// If using std::optional
/*typedef std::nullopt_t nullspec_type;
inline constexpr nullspec_type nullspec = std::nullopt;*/
// Using boost::optional
/** brief Type definition for an empty class type which indicates that a Spec value is undefined. */
typedef boost::none_t nullspec_type;
/** brief A constant which can be used as an argument to a Spec constructor with a nullspec_type
parameter.
*/
const nullspec_type nullspec = boost::none;
/** brief Exception class for attempts to access non-existent Spec values. */
class bad_spec_access : public std::logic_error
public:
/** brief Constructs a Spec bad access error */
bad_spec_access(const std::string& s) : std::logic_error(s)
;
/** brief Encapsulates the specified minimum, typical, and maximum value of a quantity.
The minimum, typical value, and/or maximum value may all be left undefined (but at least one
must be defined). This permits the construction of specifications which are unbounded (e.g.
have no minimum value) or which do not have a typical value.
The maximum is allowed to be less than the minimum, and the typical value need not be between
the minimum and maximum.
tparam T an arithmetic type or a type which behaves like an arithmetic type (has arithmetic
operators like + and - defined).
*/
template<typename T> class Spec
typedef boost::optional<T> optional_type;
//typedef std::optional<T> optional_type; std::optional alternative
optional_type minimum;
optional_type typ;
optional_type maximum;
public:
typedef T value_type;
Spec(value_type typ) : minimum(), typ(typ), maximum()
Spec(value_type minimum, value_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(value_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(nullspec_type minimum, value_type maximum) : minimum(), typ(), maximum(maximum)
Spec(value_type minimum, nullspec_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(nullspec_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(value_type minimum, value_type typ, nullspec_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
/** brief A Spec must be constructed with at least one value (minimum, typical or maximum). */
Spec() = delete;
constexpr bool has_min() const noexcept return bool(minimum);
constexpr bool has_typical() const noexcept return bool(typ);
constexpr bool has_max() const noexcept return bool(maximum);
constexpr value_type min() const
if (minimum) return *minimum;
else throw bad_spec_access("attempted to access a non-existent minimum spec");
constexpr value_type typical() const
if (typ) return *typ;
else throw bad_spec_access("attempted to access a non-existent typical spec");
constexpr value_type max() const
if (maximum) return *maximum;
else throw bad_spec_access("attempted to access a non-existent maximum spec");
/** brief Returns the minimum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined minimum.
*/
template<typename V>
constexpr value_type min_or(const V& default_value) const
return minimum.value_or(default_value);
/** brief Returns the typical value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined typical value.
*/
template<typename V>
constexpr value_type typical_or(const V& default_value) const
return typ.value_or(default_value);
/** brief Returns the maximum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined maximum.
*/
template<typename V>
constexpr value_type max_or(const V& default_value) const
return maximum.value_or(default_value);
/** brief Returns the minimum value if it exists and is greater than the supplied clamp value,
otherwise the supplied clamp value.
Similar to min_or(), except that the return value is guaranteed to be greater than or equal to the
clamp value. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might be less than clamp_minimum)
return clamp_min(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum));
/** brief Returns the minimum value if it exists and is within the range specified by the clamp values.
If the minimum value exists but is outside the range specified by the clamp values then the clamp value
closest to the minimum is returned. If the minimum value doesn't exist then the supplied clamp minimum
is returned.
Similar to min_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values.
Similar to typical_or(), except that the return value is guaranteed to be within the range specified
by the supplied clamp values. The argument to typical_or() may be used to clamp its return value to a
specified value if the Spec typical exists, but not to a range of values and not if the Spec has a
typical which is outside a clamp range.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else throw bad_spec_access("cannot clamp a non-existent typical value");
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values, or returns the specified default value.
Similar to clamped_typical(const V&, const V&), except that a default value is returned in the
case where clamped_typical(const V&, const V&) throws an exception. Also similar to typical_or(),
except that the return value is guaranteed to be within the range specified by the supplied clamp
values if the Spec typical is defined.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum, const V& default_value) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else return default_value;
/** brief Returns the maximum value if it exists and is less than the supplied clamp value,
otherwise the supplied clamp value.
Similar to max_or(), except that the return value is guaranteed to be less than or equal to the
clamp value. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is greater than the argument to max_or().
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might be greater than clamp_maximum)
return clamp_max(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_maximum));
/** brief Returns the maximum value if it exists and is within the range specified by the clamp values.
If the maximum value exists but is outside the range specified by the clamp values then the clamp value
closest to the maximum is returned. If the maximum value doesn't exist then the supplied clamp maximum
is returned.
Similar to max_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is outside the clamp range.
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Determines if two Specs are equal.
Two Specs are considered equal if all the following are true:
1. Both Specs have equal minimum values or both Specs have an undefined minimum.
2. Both Specs have equal typical values or both Specs have an undefined typical value.
3. Both Specs have equal maximum values or both Specs have an undefined maximum.
tparam U a value_type which can be cast to type `T`.
*/
template<typename T, typename U>
friend inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs);
template<typename T, typename U>
friend inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs);
/** brief Inserts a Spec into a `std::ostream`.
Inserts a string of the form '[min, typical, max]', where 'min', 'typical', and 'max' are the minimum,
typical, and maximum values of the Spec, respectively. If a minimum value is not defined then the
string "-Inf" is used (to symbolize negative infinity). If a maximum value is not defined then the
string "Inf" is used (to symbolize positive infinity). If a typical value is not defined then the
substring 'typical, ' is omitted -- the inserted string has the form '[min, max]'.
Square brackets are used to denote closed intervals if `spec` has a defined minimum and/or maximum,
and parentheses are used to denote open intervals if `spec` has an undefined minimum and/or maximum.
For example, the string for a `spec` with a minimum of 0 and an undefined maximum is '[0, Inf)`.
tparam T a type for which operator<<(std::ostream, T) is defined.
*/
template<typename T>
friend inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec);
;
template<typename T, typename U>
inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs)
// Two optionals are equal if:
// 1. they both contain a value and those values are equal
// 2. neither contains a value
return (lhs.minimum == rhs.minimum) && (lhs.typ == rhs.typ) && (lhs.maximum == rhs.maximum);
template<typename T, typename U>
inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs) return !(lhs == rhs);
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec)
if (spec.has_min()) os << '[' << spec.min();
else os << '(' << "-Inf";
os << ", ";
if (spec.has_typical()) os << spec.typical() << ", ";
if (spec.has_max()) os << spec.max() << ']';
else os << "Inf" << ')';
return os;
/** brief Clamps a value to within a specified range.
Based on std::clamp() in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp.
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr const T& clamp(const T& value, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
return value < min ? min : value > max ? max : value;
/** brief Clamps a value to greater than or equal to a specified minimum. */
template<typename T>
constexpr const T& clamp_min(const T& value, const T& min)
return value < min ? min : value;
/** brief Clamps a value to less than or equal to a specified maximum. */
template<typename T>
constexpr const T& clamp_max(const T& value, const T& max)
return value > max ? max : value;
/** brief Clamps the values held by a Spec to within a specified range.
return a new `Spec<T>`, `s`, with its minimum, typical, and maximum values clamped to the range specified
by `min` and `max`. If `spec` does not have a defined minimum then `s` has its minimum set to `min`.
If `spec` does not have a defined maximum then `s` has its maximum set to `max`. If `spec` does not
have a defined typical then `s` does not have a defined typical, either. If `spec` has a defined typical
then the defined typical for `s` is either equal to that of `spec` (if the typical is within the clamp
limits) or set to the closest clamp value (e.g. if `spec.typical() > max` then `s.typical() = max`).
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr Spec<T> clamp(const Spec<T>& spec, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
auto clamped_min = spec.clamped_min(min);
auto clamped_max = spec.clamped_max(max);
if (spec.has_typical())
auto clamped_typical = clamp(spec.typical(), min, max);
return Spec<T>(clamped_min, clamped_typical, clamped_max);
else return Spec<T>(clamped_min, clamped_max);
/** brief Determines if a value is between the limits of a Spec.
return `true` if `value` is greater than or equal to the Spec argument's minimum and less than or
equal to the Spec argument's maximum, `false` otherwise. If the Spec argument does not have a minimum
then `value` is considered greater than the Spec argument's minimum. If the Spec argument does not
have a maximum then `value` is considered less than the Spec argument's maximum. For example, if `spec`
has a minimum but no maximum then `true` is returned if `value >= spec.min()`, `false` otherwise. If
`spec` has neither a minimum nor maximum then `true` is returned for any `value`.
*/
template<typename V, typename T>
inline bool pass(const V& value, const Spec<T>& spec)
if (spec.has_min() && value < spec.min()) return false;
if (spec.has_max() && value > spec.max()) return false;
return true;
template<typename V, typename T>
inline bool fail(const V& value, const Spec<T>& spec)
return !pass(value, spec);
/** brief Calculates Spec limits based on an expected value and a % tolerance.
return a Spec with `value_type` set to the same type as `value`, a minimum equal to
`(1 - tolerance / 100) * value`, a typical equal to `value`, and a maximum equal to
`(1 + tolerance / 100) * value`. For example, for a 1% tolerance the minimum is equal to 99% of `value`
and the maximum is equal to 101% of `value`.
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance)
return Spec<V>((1 - tolerance / 100.0) * value, value, (1 + tolerance / 100.0) * value);
/** brief Calculates Spec limits based on an expected value and a % tolerance and a clamping range.
return a Spec with `value_type` set to the same type as `value`, a typical equal to `value`, a
minimum set to `(1 - tolerance / 100) * value` or `clamp_min` (whichever is greater), and a maximum
set to `(1 + tolerance / 100) * value` or `clamp_max` (whichever is less). For example, for a 1%
tolerance the minimum is equal to 99% of `value` and the maximum is equal to 101% of `value` unless
the minimum and/or maximum exceed the clamp range (in which case the minimum and/or maximum are set
to the clamp values).
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance, const V& clamp_minimum, const V& clamp_maximum)
// Min/max based on tolerance calculation
V tol_min = (1 - tolerance / 100.0) * value;
V tol_max = (1 + tolerance / 100.0) * value;
// Clamp the min/max based on the tolerance calculation to the minimum/maximum
clamp(tol_min, clamp_minimum, clamp_maximum);
clamp(tol_max, clamp_minimum, clamp_maximum);
return Spec<V>(tol_min, value, tol_max);
#endif
Here's a demo program of Spec
and the related utilities:
#include <iostream>
#include <chrono>
#include <stdexcept>
#include <vector>
#include <fstream>
#include "Spec.h"
using namespace std::chrono_literals;
template<typename T>
void print_unchecked_accesses(std::ostream& os, Spec<T> spec)
try
auto min = spec.min();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto typ = spec.typical();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto max = spec.max();
catch (const bad_spec_access& err)
os << err.what() << 'n';
os << 'n';
template<typename T>
void print_checked_accesses(std::ostream& os, Spec<T> spec)
if (spec.has_min()) auto min = spec.min();
else os << "non-existent minn";
if (spec.has_typical()) auto typ = spec.typical();
else os << "non-existent typicaln";
if (spec.has_max()) auto max = spec.max();
else os << "non-existent maxn";
os << 'n';
template<typename T, typename V>
void print_access_or(std::ostream& os, Spec<T> spec, V default_value)
os << "Accesses with default value " << default_value << " provided: ";
os << spec.min_or(default_value) << ", ";
os << spec.typical_or(default_value) << ", ";
os << spec.max_or(default_value) << "nn";
template<typename T, typename V>
void test_clamps(std::ostream& os, Spec<T> spec, V clamp_min, V clamp_max)
os << spec << " limits clamped to " << clamp(spec, clamp_min, clamp_max)
<< " with clamps " << clamp_min << " and " << clamp_max << 'n';
V default_value = 0;
os << "Clamping typical with default value " << default_value << ": "
<< spec.clamped_typical(clamp_min, clamp_max, default_value) << 'n';
os << "Clamping typical without a default value: ";
try
os << spec.clamped_typical(clamp_min, clamp_max);
catch (const bad_spec_access& err)
os << err.what();
os << 'n';
int main()
std::ofstream ofs("demo.txt");
double clamp_min = 0;
double clamp_max = 15;
std::vector<Spec<double> > specs =
Spec<double>(0, 5), // double-sided
Spec<double>(-5, nullspec), // min only
Spec<double>(nullspec, 298), // max only
Spec<double>(90), // typical only
Spec<double>(-79, 235, 89235), // min, typical, and max
Spec<double>(tolerance_spec<double, int>(5, 10)),
Spec<double>(tolerance_spec<double, int>(15, 10, clamp_min, clamp_max))
;
for (auto spec : specs)
ofs << "Spec: " << spec << 'n';
ofs << "Exceptions caught due to unchecked accesses:n";
print_unchecked_accesses(ofs, spec);
ofs << "Non-existent values determined from checked accesses:n";
print_checked_accesses(ofs, spec);
print_access_or(ofs, spec, -1);
test_clamps(ofs, spec, clamp_min, clamp_max);
ofs << "--------------------------------------------------------------------------------n";
ofs << "Testing equality:n";
using TimeSpec = Spec<std::chrono::microseconds>;
TimeSpec::value_type max = 5us;
TimeSpec t1(-15us, 0us, max);
TimeSpec t2 = t1;
TimeSpec t3(-10us, 0us, max); // unequal min
TimeSpec t4(-15us, 1us, max); // unequal typical
TimeSpec t5(-15us, 0us, 6us); // unequal max
std::vector<TimeSpec> timespecst1, t2, t3, t4, t5;
ofs << std::boolalpha;
for (std::size_t t = 1; t < timespecs.size(); t++)
ofs << "t1 == t" << t + 1 << "?: " << (t1 == timespecs[t]) << 'n';
ofs << 'n';
Spec<double> spec(-15, 0, 5);
ofs << "Testing pass/fail with Spec " << spec << "n";
for (auto value : -16, -15, 0, 5, 10)
ofs << value << " passes?: " << pass(value, spec) << 'n';
return 0;
The demo program outputs:
Spec: [0, 5]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent typical
Accesses with default value -1 provided: 0, -1, 5
[0, 5] limits clamped to [0, 5] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: [-5, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent typical
non-existent max
Accesses with default value -1 provided: -5, -1, -1
[-5, Inf) limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 298]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent min
non-existent typical
Accesses with default value -1 provided: -1, -1, 298
(-Inf, 298] limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 90, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent min
non-existent max
Accesses with default value -1 provided: -1, 90, -1
(-Inf, 90, Inf) limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [-79, 235, 89235]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: -79, 235, 89235
[-79, 235, 89235] limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [4.5, 5, 5.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 4.5, 5, 5.5
[4.5, 5, 5.5] limits clamped to [4.5, 5, 5.5] with clamps 0 and 15
Clamping typical with default value 0: 5
Clamping typical without a default value: 5
--------------------------------------------------------------------------------
Spec: [13.5, 15, 16.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 13.5, 15, 16.5
[13.5, 15, 16.5] limits clamped to [13.5, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Testing equality:
t1 == t2?: true
t1 == t3?: false
t1 == t4?: false
t1 == t5?: false
Testing pass/fail with Spec [-15, 0, 5]
-16 passes?: false
-15 passes?: true
0 passes?: true
5 passes?: true
10 passes?: false
I'm looking for suggestions regarding the class design, naming, etc. Also, a few specific concerns that I'd appreciate reviewers to comment on:
- I'm still learning how to use the full power of C++11 and C++14 since I was using a pre-C++11 compiler for a long time, so are there any C++11 or C++14 features I forgot to use which would improve the code?
- I'm not very comfortable with move semantics yet. Are there some optimizations I've missed because of that?
- I've provided no functions to modify the internal data of the
Spec
class (it is immutable). This simplifies the class design a bit (e.g. I don't have to check that at least one of the minimum/typical/maximum values are defined when modifying the instance -- I just have to delete the default constructor). However, in my usage it has occasionally been inconvenient to not be able to modify aSpec
instance after it's constructed (e.g. to acquire aSpec
instance with limits taken from another instance but clamped -- I have to copy it instead). Was my decision to make the class immutable a good one, or does it limit its usefulness? - It would be nice to detect if the code is being compiled by a compiler that supports
std::optional
and usestd::optional
if so, or try to fall back onboost::optional
if not. Is there a way to do this without too much difficulty?
c++ c++11 boost optional
add a comment |Â
up vote
7
down vote
favorite
A couple of years ago I wrote a pair of class templates to encapsulate specifications which have one or more of a minimum, typical, and/or maximum value, e.g. on a datasheet like this one for the 741 op amp:
That code required me to re-invent the wheel a bit since I was using an old compiler, and I had not discovered boost::optional
/std::optional
yet. I've re-written that code to use a single class template called Spec
, C++11 and C++14 features, and boost::optional
or std::optional
. I've also included some additional utilities (inserting a Spec
into a std::ostream
, clamping Spec
values, computing spec limits based on a value and a % tolerance, etc.). I have not added yet support for guardbands as in the previous implementation, but I plan to do so later.
Here is the implementation using boost::optional
. My compiler is Visual Studio 2017 (which can use C++17's std::optional
), but I'm only using C++14 features in the project this will be used in. I've provided commented-out code below to use std::optional
instead:
#ifndef SPEC_H
#define SPEC_H
#include <ostream>
#include <stdexcept>
// To use std::optional, if supported by compiler
//#include <optional>
// To use boost::optional
#include <boost/none.hpp> // boost::none_t
#include <boost/optional.hpp>
// If using std::optional
/*typedef std::nullopt_t nullspec_type;
inline constexpr nullspec_type nullspec = std::nullopt;*/
// Using boost::optional
/** brief Type definition for an empty class type which indicates that a Spec value is undefined. */
typedef boost::none_t nullspec_type;
/** brief A constant which can be used as an argument to a Spec constructor with a nullspec_type
parameter.
*/
const nullspec_type nullspec = boost::none;
/** brief Exception class for attempts to access non-existent Spec values. */
class bad_spec_access : public std::logic_error
public:
/** brief Constructs a Spec bad access error */
bad_spec_access(const std::string& s) : std::logic_error(s)
;
/** brief Encapsulates the specified minimum, typical, and maximum value of a quantity.
The minimum, typical value, and/or maximum value may all be left undefined (but at least one
must be defined). This permits the construction of specifications which are unbounded (e.g.
have no minimum value) or which do not have a typical value.
The maximum is allowed to be less than the minimum, and the typical value need not be between
the minimum and maximum.
tparam T an arithmetic type or a type which behaves like an arithmetic type (has arithmetic
operators like + and - defined).
*/
template<typename T> class Spec
typedef boost::optional<T> optional_type;
//typedef std::optional<T> optional_type; std::optional alternative
optional_type minimum;
optional_type typ;
optional_type maximum;
public:
typedef T value_type;
Spec(value_type typ) : minimum(), typ(typ), maximum()
Spec(value_type minimum, value_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(value_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(nullspec_type minimum, value_type maximum) : minimum(), typ(), maximum(maximum)
Spec(value_type minimum, nullspec_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(nullspec_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(value_type minimum, value_type typ, nullspec_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
/** brief A Spec must be constructed with at least one value (minimum, typical or maximum). */
Spec() = delete;
constexpr bool has_min() const noexcept return bool(minimum);
constexpr bool has_typical() const noexcept return bool(typ);
constexpr bool has_max() const noexcept return bool(maximum);
constexpr value_type min() const
if (minimum) return *minimum;
else throw bad_spec_access("attempted to access a non-existent minimum spec");
constexpr value_type typical() const
if (typ) return *typ;
else throw bad_spec_access("attempted to access a non-existent typical spec");
constexpr value_type max() const
if (maximum) return *maximum;
else throw bad_spec_access("attempted to access a non-existent maximum spec");
/** brief Returns the minimum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined minimum.
*/
template<typename V>
constexpr value_type min_or(const V& default_value) const
return minimum.value_or(default_value);
/** brief Returns the typical value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined typical value.
*/
template<typename V>
constexpr value_type typical_or(const V& default_value) const
return typ.value_or(default_value);
/** brief Returns the maximum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined maximum.
*/
template<typename V>
constexpr value_type max_or(const V& default_value) const
return maximum.value_or(default_value);
/** brief Returns the minimum value if it exists and is greater than the supplied clamp value,
otherwise the supplied clamp value.
Similar to min_or(), except that the return value is guaranteed to be greater than or equal to the
clamp value. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might be less than clamp_minimum)
return clamp_min(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum));
/** brief Returns the minimum value if it exists and is within the range specified by the clamp values.
If the minimum value exists but is outside the range specified by the clamp values then the clamp value
closest to the minimum is returned. If the minimum value doesn't exist then the supplied clamp minimum
is returned.
Similar to min_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values.
Similar to typical_or(), except that the return value is guaranteed to be within the range specified
by the supplied clamp values. The argument to typical_or() may be used to clamp its return value to a
specified value if the Spec typical exists, but not to a range of values and not if the Spec has a
typical which is outside a clamp range.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else throw bad_spec_access("cannot clamp a non-existent typical value");
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values, or returns the specified default value.
Similar to clamped_typical(const V&, const V&), except that a default value is returned in the
case where clamped_typical(const V&, const V&) throws an exception. Also similar to typical_or(),
except that the return value is guaranteed to be within the range specified by the supplied clamp
values if the Spec typical is defined.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum, const V& default_value) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else return default_value;
/** brief Returns the maximum value if it exists and is less than the supplied clamp value,
otherwise the supplied clamp value.
Similar to max_or(), except that the return value is guaranteed to be less than or equal to the
clamp value. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is greater than the argument to max_or().
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might be greater than clamp_maximum)
return clamp_max(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_maximum));
/** brief Returns the maximum value if it exists and is within the range specified by the clamp values.
If the maximum value exists but is outside the range specified by the clamp values then the clamp value
closest to the maximum is returned. If the maximum value doesn't exist then the supplied clamp maximum
is returned.
Similar to max_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is outside the clamp range.
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Determines if two Specs are equal.
Two Specs are considered equal if all the following are true:
1. Both Specs have equal minimum values or both Specs have an undefined minimum.
2. Both Specs have equal typical values or both Specs have an undefined typical value.
3. Both Specs have equal maximum values or both Specs have an undefined maximum.
tparam U a value_type which can be cast to type `T`.
*/
template<typename T, typename U>
friend inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs);
template<typename T, typename U>
friend inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs);
/** brief Inserts a Spec into a `std::ostream`.
Inserts a string of the form '[min, typical, max]', where 'min', 'typical', and 'max' are the minimum,
typical, and maximum values of the Spec, respectively. If a minimum value is not defined then the
string "-Inf" is used (to symbolize negative infinity). If a maximum value is not defined then the
string "Inf" is used (to symbolize positive infinity). If a typical value is not defined then the
substring 'typical, ' is omitted -- the inserted string has the form '[min, max]'.
Square brackets are used to denote closed intervals if `spec` has a defined minimum and/or maximum,
and parentheses are used to denote open intervals if `spec` has an undefined minimum and/or maximum.
For example, the string for a `spec` with a minimum of 0 and an undefined maximum is '[0, Inf)`.
tparam T a type for which operator<<(std::ostream, T) is defined.
*/
template<typename T>
friend inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec);
;
template<typename T, typename U>
inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs)
// Two optionals are equal if:
// 1. they both contain a value and those values are equal
// 2. neither contains a value
return (lhs.minimum == rhs.minimum) && (lhs.typ == rhs.typ) && (lhs.maximum == rhs.maximum);
template<typename T, typename U>
inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs) return !(lhs == rhs);
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec)
if (spec.has_min()) os << '[' << spec.min();
else os << '(' << "-Inf";
os << ", ";
if (spec.has_typical()) os << spec.typical() << ", ";
if (spec.has_max()) os << spec.max() << ']';
else os << "Inf" << ')';
return os;
/** brief Clamps a value to within a specified range.
Based on std::clamp() in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp.
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr const T& clamp(const T& value, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
return value < min ? min : value > max ? max : value;
/** brief Clamps a value to greater than or equal to a specified minimum. */
template<typename T>
constexpr const T& clamp_min(const T& value, const T& min)
return value < min ? min : value;
/** brief Clamps a value to less than or equal to a specified maximum. */
template<typename T>
constexpr const T& clamp_max(const T& value, const T& max)
return value > max ? max : value;
/** brief Clamps the values held by a Spec to within a specified range.
return a new `Spec<T>`, `s`, with its minimum, typical, and maximum values clamped to the range specified
by `min` and `max`. If `spec` does not have a defined minimum then `s` has its minimum set to `min`.
If `spec` does not have a defined maximum then `s` has its maximum set to `max`. If `spec` does not
have a defined typical then `s` does not have a defined typical, either. If `spec` has a defined typical
then the defined typical for `s` is either equal to that of `spec` (if the typical is within the clamp
limits) or set to the closest clamp value (e.g. if `spec.typical() > max` then `s.typical() = max`).
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr Spec<T> clamp(const Spec<T>& spec, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
auto clamped_min = spec.clamped_min(min);
auto clamped_max = spec.clamped_max(max);
if (spec.has_typical())
auto clamped_typical = clamp(spec.typical(), min, max);
return Spec<T>(clamped_min, clamped_typical, clamped_max);
else return Spec<T>(clamped_min, clamped_max);
/** brief Determines if a value is between the limits of a Spec.
return `true` if `value` is greater than or equal to the Spec argument's minimum and less than or
equal to the Spec argument's maximum, `false` otherwise. If the Spec argument does not have a minimum
then `value` is considered greater than the Spec argument's minimum. If the Spec argument does not
have a maximum then `value` is considered less than the Spec argument's maximum. For example, if `spec`
has a minimum but no maximum then `true` is returned if `value >= spec.min()`, `false` otherwise. If
`spec` has neither a minimum nor maximum then `true` is returned for any `value`.
*/
template<typename V, typename T>
inline bool pass(const V& value, const Spec<T>& spec)
if (spec.has_min() && value < spec.min()) return false;
if (spec.has_max() && value > spec.max()) return false;
return true;
template<typename V, typename T>
inline bool fail(const V& value, const Spec<T>& spec)
return !pass(value, spec);
/** brief Calculates Spec limits based on an expected value and a % tolerance.
return a Spec with `value_type` set to the same type as `value`, a minimum equal to
`(1 - tolerance / 100) * value`, a typical equal to `value`, and a maximum equal to
`(1 + tolerance / 100) * value`. For example, for a 1% tolerance the minimum is equal to 99% of `value`
and the maximum is equal to 101% of `value`.
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance)
return Spec<V>((1 - tolerance / 100.0) * value, value, (1 + tolerance / 100.0) * value);
/** brief Calculates Spec limits based on an expected value and a % tolerance and a clamping range.
return a Spec with `value_type` set to the same type as `value`, a typical equal to `value`, a
minimum set to `(1 - tolerance / 100) * value` or `clamp_min` (whichever is greater), and a maximum
set to `(1 + tolerance / 100) * value` or `clamp_max` (whichever is less). For example, for a 1%
tolerance the minimum is equal to 99% of `value` and the maximum is equal to 101% of `value` unless
the minimum and/or maximum exceed the clamp range (in which case the minimum and/or maximum are set
to the clamp values).
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance, const V& clamp_minimum, const V& clamp_maximum)
// Min/max based on tolerance calculation
V tol_min = (1 - tolerance / 100.0) * value;
V tol_max = (1 + tolerance / 100.0) * value;
// Clamp the min/max based on the tolerance calculation to the minimum/maximum
clamp(tol_min, clamp_minimum, clamp_maximum);
clamp(tol_max, clamp_minimum, clamp_maximum);
return Spec<V>(tol_min, value, tol_max);
#endif
Here's a demo program of Spec
and the related utilities:
#include <iostream>
#include <chrono>
#include <stdexcept>
#include <vector>
#include <fstream>
#include "Spec.h"
using namespace std::chrono_literals;
template<typename T>
void print_unchecked_accesses(std::ostream& os, Spec<T> spec)
try
auto min = spec.min();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto typ = spec.typical();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto max = spec.max();
catch (const bad_spec_access& err)
os << err.what() << 'n';
os << 'n';
template<typename T>
void print_checked_accesses(std::ostream& os, Spec<T> spec)
if (spec.has_min()) auto min = spec.min();
else os << "non-existent minn";
if (spec.has_typical()) auto typ = spec.typical();
else os << "non-existent typicaln";
if (spec.has_max()) auto max = spec.max();
else os << "non-existent maxn";
os << 'n';
template<typename T, typename V>
void print_access_or(std::ostream& os, Spec<T> spec, V default_value)
os << "Accesses with default value " << default_value << " provided: ";
os << spec.min_or(default_value) << ", ";
os << spec.typical_or(default_value) << ", ";
os << spec.max_or(default_value) << "nn";
template<typename T, typename V>
void test_clamps(std::ostream& os, Spec<T> spec, V clamp_min, V clamp_max)
os << spec << " limits clamped to " << clamp(spec, clamp_min, clamp_max)
<< " with clamps " << clamp_min << " and " << clamp_max << 'n';
V default_value = 0;
os << "Clamping typical with default value " << default_value << ": "
<< spec.clamped_typical(clamp_min, clamp_max, default_value) << 'n';
os << "Clamping typical without a default value: ";
try
os << spec.clamped_typical(clamp_min, clamp_max);
catch (const bad_spec_access& err)
os << err.what();
os << 'n';
int main()
std::ofstream ofs("demo.txt");
double clamp_min = 0;
double clamp_max = 15;
std::vector<Spec<double> > specs =
Spec<double>(0, 5), // double-sided
Spec<double>(-5, nullspec), // min only
Spec<double>(nullspec, 298), // max only
Spec<double>(90), // typical only
Spec<double>(-79, 235, 89235), // min, typical, and max
Spec<double>(tolerance_spec<double, int>(5, 10)),
Spec<double>(tolerance_spec<double, int>(15, 10, clamp_min, clamp_max))
;
for (auto spec : specs)
ofs << "Spec: " << spec << 'n';
ofs << "Exceptions caught due to unchecked accesses:n";
print_unchecked_accesses(ofs, spec);
ofs << "Non-existent values determined from checked accesses:n";
print_checked_accesses(ofs, spec);
print_access_or(ofs, spec, -1);
test_clamps(ofs, spec, clamp_min, clamp_max);
ofs << "--------------------------------------------------------------------------------n";
ofs << "Testing equality:n";
using TimeSpec = Spec<std::chrono::microseconds>;
TimeSpec::value_type max = 5us;
TimeSpec t1(-15us, 0us, max);
TimeSpec t2 = t1;
TimeSpec t3(-10us, 0us, max); // unequal min
TimeSpec t4(-15us, 1us, max); // unequal typical
TimeSpec t5(-15us, 0us, 6us); // unequal max
std::vector<TimeSpec> timespecst1, t2, t3, t4, t5;
ofs << std::boolalpha;
for (std::size_t t = 1; t < timespecs.size(); t++)
ofs << "t1 == t" << t + 1 << "?: " << (t1 == timespecs[t]) << 'n';
ofs << 'n';
Spec<double> spec(-15, 0, 5);
ofs << "Testing pass/fail with Spec " << spec << "n";
for (auto value : -16, -15, 0, 5, 10)
ofs << value << " passes?: " << pass(value, spec) << 'n';
return 0;
The demo program outputs:
Spec: [0, 5]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent typical
Accesses with default value -1 provided: 0, -1, 5
[0, 5] limits clamped to [0, 5] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: [-5, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent typical
non-existent max
Accesses with default value -1 provided: -5, -1, -1
[-5, Inf) limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 298]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent min
non-existent typical
Accesses with default value -1 provided: -1, -1, 298
(-Inf, 298] limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 90, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent min
non-existent max
Accesses with default value -1 provided: -1, 90, -1
(-Inf, 90, Inf) limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [-79, 235, 89235]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: -79, 235, 89235
[-79, 235, 89235] limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [4.5, 5, 5.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 4.5, 5, 5.5
[4.5, 5, 5.5] limits clamped to [4.5, 5, 5.5] with clamps 0 and 15
Clamping typical with default value 0: 5
Clamping typical without a default value: 5
--------------------------------------------------------------------------------
Spec: [13.5, 15, 16.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 13.5, 15, 16.5
[13.5, 15, 16.5] limits clamped to [13.5, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Testing equality:
t1 == t2?: true
t1 == t3?: false
t1 == t4?: false
t1 == t5?: false
Testing pass/fail with Spec [-15, 0, 5]
-16 passes?: false
-15 passes?: true
0 passes?: true
5 passes?: true
10 passes?: false
I'm looking for suggestions regarding the class design, naming, etc. Also, a few specific concerns that I'd appreciate reviewers to comment on:
- I'm still learning how to use the full power of C++11 and C++14 since I was using a pre-C++11 compiler for a long time, so are there any C++11 or C++14 features I forgot to use which would improve the code?
- I'm not very comfortable with move semantics yet. Are there some optimizations I've missed because of that?
- I've provided no functions to modify the internal data of the
Spec
class (it is immutable). This simplifies the class design a bit (e.g. I don't have to check that at least one of the minimum/typical/maximum values are defined when modifying the instance -- I just have to delete the default constructor). However, in my usage it has occasionally been inconvenient to not be able to modify aSpec
instance after it's constructed (e.g. to acquire aSpec
instance with limits taken from another instance but clamped -- I have to copy it instead). Was my decision to make the class immutable a good one, or does it limit its usefulness? - It would be nice to detect if the code is being compiled by a compiler that supports
std::optional
and usestd::optional
if so, or try to fall back onboost::optional
if not. Is there a way to do this without too much difficulty?
c++ c++11 boost optional
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
A couple of years ago I wrote a pair of class templates to encapsulate specifications which have one or more of a minimum, typical, and/or maximum value, e.g. on a datasheet like this one for the 741 op amp:
That code required me to re-invent the wheel a bit since I was using an old compiler, and I had not discovered boost::optional
/std::optional
yet. I've re-written that code to use a single class template called Spec
, C++11 and C++14 features, and boost::optional
or std::optional
. I've also included some additional utilities (inserting a Spec
into a std::ostream
, clamping Spec
values, computing spec limits based on a value and a % tolerance, etc.). I have not added yet support for guardbands as in the previous implementation, but I plan to do so later.
Here is the implementation using boost::optional
. My compiler is Visual Studio 2017 (which can use C++17's std::optional
), but I'm only using C++14 features in the project this will be used in. I've provided commented-out code below to use std::optional
instead:
#ifndef SPEC_H
#define SPEC_H
#include <ostream>
#include <stdexcept>
// To use std::optional, if supported by compiler
//#include <optional>
// To use boost::optional
#include <boost/none.hpp> // boost::none_t
#include <boost/optional.hpp>
// If using std::optional
/*typedef std::nullopt_t nullspec_type;
inline constexpr nullspec_type nullspec = std::nullopt;*/
// Using boost::optional
/** brief Type definition for an empty class type which indicates that a Spec value is undefined. */
typedef boost::none_t nullspec_type;
/** brief A constant which can be used as an argument to a Spec constructor with a nullspec_type
parameter.
*/
const nullspec_type nullspec = boost::none;
/** brief Exception class for attempts to access non-existent Spec values. */
class bad_spec_access : public std::logic_error
public:
/** brief Constructs a Spec bad access error */
bad_spec_access(const std::string& s) : std::logic_error(s)
;
/** brief Encapsulates the specified minimum, typical, and maximum value of a quantity.
The minimum, typical value, and/or maximum value may all be left undefined (but at least one
must be defined). This permits the construction of specifications which are unbounded (e.g.
have no minimum value) or which do not have a typical value.
The maximum is allowed to be less than the minimum, and the typical value need not be between
the minimum and maximum.
tparam T an arithmetic type or a type which behaves like an arithmetic type (has arithmetic
operators like + and - defined).
*/
template<typename T> class Spec
typedef boost::optional<T> optional_type;
//typedef std::optional<T> optional_type; std::optional alternative
optional_type minimum;
optional_type typ;
optional_type maximum;
public:
typedef T value_type;
Spec(value_type typ) : minimum(), typ(typ), maximum()
Spec(value_type minimum, value_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(value_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(nullspec_type minimum, value_type maximum) : minimum(), typ(), maximum(maximum)
Spec(value_type minimum, nullspec_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(nullspec_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(value_type minimum, value_type typ, nullspec_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
/** brief A Spec must be constructed with at least one value (minimum, typical or maximum). */
Spec() = delete;
constexpr bool has_min() const noexcept return bool(minimum);
constexpr bool has_typical() const noexcept return bool(typ);
constexpr bool has_max() const noexcept return bool(maximum);
constexpr value_type min() const
if (minimum) return *minimum;
else throw bad_spec_access("attempted to access a non-existent minimum spec");
constexpr value_type typical() const
if (typ) return *typ;
else throw bad_spec_access("attempted to access a non-existent typical spec");
constexpr value_type max() const
if (maximum) return *maximum;
else throw bad_spec_access("attempted to access a non-existent maximum spec");
/** brief Returns the minimum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined minimum.
*/
template<typename V>
constexpr value_type min_or(const V& default_value) const
return minimum.value_or(default_value);
/** brief Returns the typical value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined typical value.
*/
template<typename V>
constexpr value_type typical_or(const V& default_value) const
return typ.value_or(default_value);
/** brief Returns the maximum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined maximum.
*/
template<typename V>
constexpr value_type max_or(const V& default_value) const
return maximum.value_or(default_value);
/** brief Returns the minimum value if it exists and is greater than the supplied clamp value,
otherwise the supplied clamp value.
Similar to min_or(), except that the return value is guaranteed to be greater than or equal to the
clamp value. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might be less than clamp_minimum)
return clamp_min(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum));
/** brief Returns the minimum value if it exists and is within the range specified by the clamp values.
If the minimum value exists but is outside the range specified by the clamp values then the clamp value
closest to the minimum is returned. If the minimum value doesn't exist then the supplied clamp minimum
is returned.
Similar to min_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values.
Similar to typical_or(), except that the return value is guaranteed to be within the range specified
by the supplied clamp values. The argument to typical_or() may be used to clamp its return value to a
specified value if the Spec typical exists, but not to a range of values and not if the Spec has a
typical which is outside a clamp range.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else throw bad_spec_access("cannot clamp a non-existent typical value");
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values, or returns the specified default value.
Similar to clamped_typical(const V&, const V&), except that a default value is returned in the
case where clamped_typical(const V&, const V&) throws an exception. Also similar to typical_or(),
except that the return value is guaranteed to be within the range specified by the supplied clamp
values if the Spec typical is defined.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum, const V& default_value) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else return default_value;
/** brief Returns the maximum value if it exists and is less than the supplied clamp value,
otherwise the supplied clamp value.
Similar to max_or(), except that the return value is guaranteed to be less than or equal to the
clamp value. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is greater than the argument to max_or().
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might be greater than clamp_maximum)
return clamp_max(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_maximum));
/** brief Returns the maximum value if it exists and is within the range specified by the clamp values.
If the maximum value exists but is outside the range specified by the clamp values then the clamp value
closest to the maximum is returned. If the maximum value doesn't exist then the supplied clamp maximum
is returned.
Similar to max_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is outside the clamp range.
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Determines if two Specs are equal.
Two Specs are considered equal if all the following are true:
1. Both Specs have equal minimum values or both Specs have an undefined minimum.
2. Both Specs have equal typical values or both Specs have an undefined typical value.
3. Both Specs have equal maximum values or both Specs have an undefined maximum.
tparam U a value_type which can be cast to type `T`.
*/
template<typename T, typename U>
friend inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs);
template<typename T, typename U>
friend inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs);
/** brief Inserts a Spec into a `std::ostream`.
Inserts a string of the form '[min, typical, max]', where 'min', 'typical', and 'max' are the minimum,
typical, and maximum values of the Spec, respectively. If a minimum value is not defined then the
string "-Inf" is used (to symbolize negative infinity). If a maximum value is not defined then the
string "Inf" is used (to symbolize positive infinity). If a typical value is not defined then the
substring 'typical, ' is omitted -- the inserted string has the form '[min, max]'.
Square brackets are used to denote closed intervals if `spec` has a defined minimum and/or maximum,
and parentheses are used to denote open intervals if `spec` has an undefined minimum and/or maximum.
For example, the string for a `spec` with a minimum of 0 and an undefined maximum is '[0, Inf)`.
tparam T a type for which operator<<(std::ostream, T) is defined.
*/
template<typename T>
friend inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec);
;
template<typename T, typename U>
inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs)
// Two optionals are equal if:
// 1. they both contain a value and those values are equal
// 2. neither contains a value
return (lhs.minimum == rhs.minimum) && (lhs.typ == rhs.typ) && (lhs.maximum == rhs.maximum);
template<typename T, typename U>
inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs) return !(lhs == rhs);
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec)
if (spec.has_min()) os << '[' << spec.min();
else os << '(' << "-Inf";
os << ", ";
if (spec.has_typical()) os << spec.typical() << ", ";
if (spec.has_max()) os << spec.max() << ']';
else os << "Inf" << ')';
return os;
/** brief Clamps a value to within a specified range.
Based on std::clamp() in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp.
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr const T& clamp(const T& value, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
return value < min ? min : value > max ? max : value;
/** brief Clamps a value to greater than or equal to a specified minimum. */
template<typename T>
constexpr const T& clamp_min(const T& value, const T& min)
return value < min ? min : value;
/** brief Clamps a value to less than or equal to a specified maximum. */
template<typename T>
constexpr const T& clamp_max(const T& value, const T& max)
return value > max ? max : value;
/** brief Clamps the values held by a Spec to within a specified range.
return a new `Spec<T>`, `s`, with its minimum, typical, and maximum values clamped to the range specified
by `min` and `max`. If `spec` does not have a defined minimum then `s` has its minimum set to `min`.
If `spec` does not have a defined maximum then `s` has its maximum set to `max`. If `spec` does not
have a defined typical then `s` does not have a defined typical, either. If `spec` has a defined typical
then the defined typical for `s` is either equal to that of `spec` (if the typical is within the clamp
limits) or set to the closest clamp value (e.g. if `spec.typical() > max` then `s.typical() = max`).
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr Spec<T> clamp(const Spec<T>& spec, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
auto clamped_min = spec.clamped_min(min);
auto clamped_max = spec.clamped_max(max);
if (spec.has_typical())
auto clamped_typical = clamp(spec.typical(), min, max);
return Spec<T>(clamped_min, clamped_typical, clamped_max);
else return Spec<T>(clamped_min, clamped_max);
/** brief Determines if a value is between the limits of a Spec.
return `true` if `value` is greater than or equal to the Spec argument's minimum and less than or
equal to the Spec argument's maximum, `false` otherwise. If the Spec argument does not have a minimum
then `value` is considered greater than the Spec argument's minimum. If the Spec argument does not
have a maximum then `value` is considered less than the Spec argument's maximum. For example, if `spec`
has a minimum but no maximum then `true` is returned if `value >= spec.min()`, `false` otherwise. If
`spec` has neither a minimum nor maximum then `true` is returned for any `value`.
*/
template<typename V, typename T>
inline bool pass(const V& value, const Spec<T>& spec)
if (spec.has_min() && value < spec.min()) return false;
if (spec.has_max() && value > spec.max()) return false;
return true;
template<typename V, typename T>
inline bool fail(const V& value, const Spec<T>& spec)
return !pass(value, spec);
/** brief Calculates Spec limits based on an expected value and a % tolerance.
return a Spec with `value_type` set to the same type as `value`, a minimum equal to
`(1 - tolerance / 100) * value`, a typical equal to `value`, and a maximum equal to
`(1 + tolerance / 100) * value`. For example, for a 1% tolerance the minimum is equal to 99% of `value`
and the maximum is equal to 101% of `value`.
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance)
return Spec<V>((1 - tolerance / 100.0) * value, value, (1 + tolerance / 100.0) * value);
/** brief Calculates Spec limits based on an expected value and a % tolerance and a clamping range.
return a Spec with `value_type` set to the same type as `value`, a typical equal to `value`, a
minimum set to `(1 - tolerance / 100) * value` or `clamp_min` (whichever is greater), and a maximum
set to `(1 + tolerance / 100) * value` or `clamp_max` (whichever is less). For example, for a 1%
tolerance the minimum is equal to 99% of `value` and the maximum is equal to 101% of `value` unless
the minimum and/or maximum exceed the clamp range (in which case the minimum and/or maximum are set
to the clamp values).
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance, const V& clamp_minimum, const V& clamp_maximum)
// Min/max based on tolerance calculation
V tol_min = (1 - tolerance / 100.0) * value;
V tol_max = (1 + tolerance / 100.0) * value;
// Clamp the min/max based on the tolerance calculation to the minimum/maximum
clamp(tol_min, clamp_minimum, clamp_maximum);
clamp(tol_max, clamp_minimum, clamp_maximum);
return Spec<V>(tol_min, value, tol_max);
#endif
Here's a demo program of Spec
and the related utilities:
#include <iostream>
#include <chrono>
#include <stdexcept>
#include <vector>
#include <fstream>
#include "Spec.h"
using namespace std::chrono_literals;
template<typename T>
void print_unchecked_accesses(std::ostream& os, Spec<T> spec)
try
auto min = spec.min();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto typ = spec.typical();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto max = spec.max();
catch (const bad_spec_access& err)
os << err.what() << 'n';
os << 'n';
template<typename T>
void print_checked_accesses(std::ostream& os, Spec<T> spec)
if (spec.has_min()) auto min = spec.min();
else os << "non-existent minn";
if (spec.has_typical()) auto typ = spec.typical();
else os << "non-existent typicaln";
if (spec.has_max()) auto max = spec.max();
else os << "non-existent maxn";
os << 'n';
template<typename T, typename V>
void print_access_or(std::ostream& os, Spec<T> spec, V default_value)
os << "Accesses with default value " << default_value << " provided: ";
os << spec.min_or(default_value) << ", ";
os << spec.typical_or(default_value) << ", ";
os << spec.max_or(default_value) << "nn";
template<typename T, typename V>
void test_clamps(std::ostream& os, Spec<T> spec, V clamp_min, V clamp_max)
os << spec << " limits clamped to " << clamp(spec, clamp_min, clamp_max)
<< " with clamps " << clamp_min << " and " << clamp_max << 'n';
V default_value = 0;
os << "Clamping typical with default value " << default_value << ": "
<< spec.clamped_typical(clamp_min, clamp_max, default_value) << 'n';
os << "Clamping typical without a default value: ";
try
os << spec.clamped_typical(clamp_min, clamp_max);
catch (const bad_spec_access& err)
os << err.what();
os << 'n';
int main()
std::ofstream ofs("demo.txt");
double clamp_min = 0;
double clamp_max = 15;
std::vector<Spec<double> > specs =
Spec<double>(0, 5), // double-sided
Spec<double>(-5, nullspec), // min only
Spec<double>(nullspec, 298), // max only
Spec<double>(90), // typical only
Spec<double>(-79, 235, 89235), // min, typical, and max
Spec<double>(tolerance_spec<double, int>(5, 10)),
Spec<double>(tolerance_spec<double, int>(15, 10, clamp_min, clamp_max))
;
for (auto spec : specs)
ofs << "Spec: " << spec << 'n';
ofs << "Exceptions caught due to unchecked accesses:n";
print_unchecked_accesses(ofs, spec);
ofs << "Non-existent values determined from checked accesses:n";
print_checked_accesses(ofs, spec);
print_access_or(ofs, spec, -1);
test_clamps(ofs, spec, clamp_min, clamp_max);
ofs << "--------------------------------------------------------------------------------n";
ofs << "Testing equality:n";
using TimeSpec = Spec<std::chrono::microseconds>;
TimeSpec::value_type max = 5us;
TimeSpec t1(-15us, 0us, max);
TimeSpec t2 = t1;
TimeSpec t3(-10us, 0us, max); // unequal min
TimeSpec t4(-15us, 1us, max); // unequal typical
TimeSpec t5(-15us, 0us, 6us); // unequal max
std::vector<TimeSpec> timespecst1, t2, t3, t4, t5;
ofs << std::boolalpha;
for (std::size_t t = 1; t < timespecs.size(); t++)
ofs << "t1 == t" << t + 1 << "?: " << (t1 == timespecs[t]) << 'n';
ofs << 'n';
Spec<double> spec(-15, 0, 5);
ofs << "Testing pass/fail with Spec " << spec << "n";
for (auto value : -16, -15, 0, 5, 10)
ofs << value << " passes?: " << pass(value, spec) << 'n';
return 0;
The demo program outputs:
Spec: [0, 5]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent typical
Accesses with default value -1 provided: 0, -1, 5
[0, 5] limits clamped to [0, 5] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: [-5, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent typical
non-existent max
Accesses with default value -1 provided: -5, -1, -1
[-5, Inf) limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 298]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent min
non-existent typical
Accesses with default value -1 provided: -1, -1, 298
(-Inf, 298] limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 90, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent min
non-existent max
Accesses with default value -1 provided: -1, 90, -1
(-Inf, 90, Inf) limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [-79, 235, 89235]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: -79, 235, 89235
[-79, 235, 89235] limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [4.5, 5, 5.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 4.5, 5, 5.5
[4.5, 5, 5.5] limits clamped to [4.5, 5, 5.5] with clamps 0 and 15
Clamping typical with default value 0: 5
Clamping typical without a default value: 5
--------------------------------------------------------------------------------
Spec: [13.5, 15, 16.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 13.5, 15, 16.5
[13.5, 15, 16.5] limits clamped to [13.5, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Testing equality:
t1 == t2?: true
t1 == t3?: false
t1 == t4?: false
t1 == t5?: false
Testing pass/fail with Spec [-15, 0, 5]
-16 passes?: false
-15 passes?: true
0 passes?: true
5 passes?: true
10 passes?: false
I'm looking for suggestions regarding the class design, naming, etc. Also, a few specific concerns that I'd appreciate reviewers to comment on:
- I'm still learning how to use the full power of C++11 and C++14 since I was using a pre-C++11 compiler for a long time, so are there any C++11 or C++14 features I forgot to use which would improve the code?
- I'm not very comfortable with move semantics yet. Are there some optimizations I've missed because of that?
- I've provided no functions to modify the internal data of the
Spec
class (it is immutable). This simplifies the class design a bit (e.g. I don't have to check that at least one of the minimum/typical/maximum values are defined when modifying the instance -- I just have to delete the default constructor). However, in my usage it has occasionally been inconvenient to not be able to modify aSpec
instance after it's constructed (e.g. to acquire aSpec
instance with limits taken from another instance but clamped -- I have to copy it instead). Was my decision to make the class immutable a good one, or does it limit its usefulness? - It would be nice to detect if the code is being compiled by a compiler that supports
std::optional
and usestd::optional
if so, or try to fall back onboost::optional
if not. Is there a way to do this without too much difficulty?
c++ c++11 boost optional
A couple of years ago I wrote a pair of class templates to encapsulate specifications which have one or more of a minimum, typical, and/or maximum value, e.g. on a datasheet like this one for the 741 op amp:
That code required me to re-invent the wheel a bit since I was using an old compiler, and I had not discovered boost::optional
/std::optional
yet. I've re-written that code to use a single class template called Spec
, C++11 and C++14 features, and boost::optional
or std::optional
. I've also included some additional utilities (inserting a Spec
into a std::ostream
, clamping Spec
values, computing spec limits based on a value and a % tolerance, etc.). I have not added yet support for guardbands as in the previous implementation, but I plan to do so later.
Here is the implementation using boost::optional
. My compiler is Visual Studio 2017 (which can use C++17's std::optional
), but I'm only using C++14 features in the project this will be used in. I've provided commented-out code below to use std::optional
instead:
#ifndef SPEC_H
#define SPEC_H
#include <ostream>
#include <stdexcept>
// To use std::optional, if supported by compiler
//#include <optional>
// To use boost::optional
#include <boost/none.hpp> // boost::none_t
#include <boost/optional.hpp>
// If using std::optional
/*typedef std::nullopt_t nullspec_type;
inline constexpr nullspec_type nullspec = std::nullopt;*/
// Using boost::optional
/** brief Type definition for an empty class type which indicates that a Spec value is undefined. */
typedef boost::none_t nullspec_type;
/** brief A constant which can be used as an argument to a Spec constructor with a nullspec_type
parameter.
*/
const nullspec_type nullspec = boost::none;
/** brief Exception class for attempts to access non-existent Spec values. */
class bad_spec_access : public std::logic_error
public:
/** brief Constructs a Spec bad access error */
bad_spec_access(const std::string& s) : std::logic_error(s)
;
/** brief Encapsulates the specified minimum, typical, and maximum value of a quantity.
The minimum, typical value, and/or maximum value may all be left undefined (but at least one
must be defined). This permits the construction of specifications which are unbounded (e.g.
have no minimum value) or which do not have a typical value.
The maximum is allowed to be less than the minimum, and the typical value need not be between
the minimum and maximum.
tparam T an arithmetic type or a type which behaves like an arithmetic type (has arithmetic
operators like + and - defined).
*/
template<typename T> class Spec
typedef boost::optional<T> optional_type;
//typedef std::optional<T> optional_type; std::optional alternative
optional_type minimum;
optional_type typ;
optional_type maximum;
public:
typedef T value_type;
Spec(value_type typ) : minimum(), typ(typ), maximum()
Spec(value_type minimum, value_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(value_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(nullspec_type minimum, value_type maximum) : minimum(), typ(), maximum(maximum)
Spec(value_type minimum, nullspec_type maximum) : minimum(minimum), typ(), maximum(maximum)
Spec(nullspec_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
Spec(value_type minimum, value_type typ, nullspec_type maximum) : minimum(minimum), typ(typ), maximum(maximum)
/** brief A Spec must be constructed with at least one value (minimum, typical or maximum). */
Spec() = delete;
constexpr bool has_min() const noexcept return bool(minimum);
constexpr bool has_typical() const noexcept return bool(typ);
constexpr bool has_max() const noexcept return bool(maximum);
constexpr value_type min() const
if (minimum) return *minimum;
else throw bad_spec_access("attempted to access a non-existent minimum spec");
constexpr value_type typical() const
if (typ) return *typ;
else throw bad_spec_access("attempted to access a non-existent typical spec");
constexpr value_type max() const
if (maximum) return *maximum;
else throw bad_spec_access("attempted to access a non-existent maximum spec");
/** brief Returns the minimum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined minimum.
*/
template<typename V>
constexpr value_type min_or(const V& default_value) const
return minimum.value_or(default_value);
/** brief Returns the typical value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined typical value.
*/
template<typename V>
constexpr value_type typical_or(const V& default_value) const
return typ.value_or(default_value);
/** brief Returns the maximum value if it exists, or the supplied value if not.
tparam V a type which can be cast to ref value_type.
param[in] default_value the value to return if the Spec does not have a defined maximum.
*/
template<typename V>
constexpr value_type max_or(const V& default_value) const
return maximum.value_or(default_value);
/** brief Returns the minimum value if it exists and is greater than the supplied clamp value,
otherwise the supplied clamp value.
Similar to min_or(), except that the return value is guaranteed to be greater than or equal to the
clamp value. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might be less than clamp_minimum)
return clamp_min(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum));
/** brief Returns the minimum value if it exists and is within the range specified by the clamp values.
If the minimum value exists but is outside the range specified by the clamp values then the clamp value
closest to the minimum is returned. If the minimum value doesn't exist then the supplied clamp minimum
is returned.
Similar to min_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to min_or() may be used to clamp its return value to a specified value if
the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
*/
template<typename V>
constexpr value_type clamped_min(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the minimum value, or use clamp_minimum if the minimum has no value
// Clamp that value (if minimum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(minimum.value_or(clamp_minimum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values.
Similar to typical_or(), except that the return value is guaranteed to be within the range specified
by the supplied clamp values. The argument to typical_or() may be used to clamp its return value to a
specified value if the Spec typical exists, but not to a range of values and not if the Spec has a
typical which is outside a clamp range.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else throw bad_spec_access("cannot clamp a non-existent typical value");
/** brief Clamps the typical value if it exists to within the range specified by the supplied
clamp values, or returns the specified default value.
Similar to clamped_typical(const V&, const V&), except that a default value is returned in the
case where clamped_typical(const V&, const V&) throws an exception. Also similar to typical_or(),
except that the return value is guaranteed to be within the range specified by the supplied clamp
values if the Spec typical is defined.
*/
template<typename V>
constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum, const V& default_value) const
if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
else return default_value;
/** brief Returns the maximum value if it exists and is less than the supplied clamp value,
otherwise the supplied clamp value.
Similar to max_or(), except that the return value is guaranteed to be less than or equal to the
clamp value. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is greater than the argument to max_or().
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might be greater than clamp_maximum)
return clamp_max(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_maximum));
/** brief Returns the maximum value if it exists and is within the range specified by the clamp values.
If the maximum value exists but is outside the range specified by the clamp values then the clamp value
closest to the maximum is returned. If the maximum value doesn't exist then the supplied clamp maximum
is returned.
Similar to max_or(), except that the return value is guaranteed to be within the range of the clamp
values. The argument to max_or() may be used to clamp its return value to a specified value if
the Spec maximum exists, but not if the Spec has a maximum which is outside the clamp range.
*/
template<typename V>
constexpr value_type clamped_max(const V& clamp_minimum, const V& clamp_maximum) const
// Acquire the maximum value, or use clamp_maximum if the maximum has no value
// Clamp that value (if maximum has a value it might not be within the range of clamp_minimum to clamp_maximum)
return clamp(static_cast<value_type>(maximum.value_or(clamp_maximum)),
static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
/** brief Determines if two Specs are equal.
Two Specs are considered equal if all the following are true:
1. Both Specs have equal minimum values or both Specs have an undefined minimum.
2. Both Specs have equal typical values or both Specs have an undefined typical value.
3. Both Specs have equal maximum values or both Specs have an undefined maximum.
tparam U a value_type which can be cast to type `T`.
*/
template<typename T, typename U>
friend inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs);
template<typename T, typename U>
friend inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs);
/** brief Inserts a Spec into a `std::ostream`.
Inserts a string of the form '[min, typical, max]', where 'min', 'typical', and 'max' are the minimum,
typical, and maximum values of the Spec, respectively. If a minimum value is not defined then the
string "-Inf" is used (to symbolize negative infinity). If a maximum value is not defined then the
string "Inf" is used (to symbolize positive infinity). If a typical value is not defined then the
substring 'typical, ' is omitted -- the inserted string has the form '[min, max]'.
Square brackets are used to denote closed intervals if `spec` has a defined minimum and/or maximum,
and parentheses are used to denote open intervals if `spec` has an undefined minimum and/or maximum.
For example, the string for a `spec` with a minimum of 0 and an undefined maximum is '[0, Inf)`.
tparam T a type for which operator<<(std::ostream, T) is defined.
*/
template<typename T>
friend inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec);
;
template<typename T, typename U>
inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs)
// Two optionals are equal if:
// 1. they both contain a value and those values are equal
// 2. neither contains a value
return (lhs.minimum == rhs.minimum) && (lhs.typ == rhs.typ) && (lhs.maximum == rhs.maximum);
template<typename T, typename U>
inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs) return !(lhs == rhs);
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec)
if (spec.has_min()) os << '[' << spec.min();
else os << '(' << "-Inf";
os << ", ";
if (spec.has_typical()) os << spec.typical() << ", ";
if (spec.has_max()) os << spec.max() << ']';
else os << "Inf" << ')';
return os;
/** brief Clamps a value to within a specified range.
Based on std::clamp() in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp.
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr const T& clamp(const T& value, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
return value < min ? min : value > max ? max : value;
/** brief Clamps a value to greater than or equal to a specified minimum. */
template<typename T>
constexpr const T& clamp_min(const T& value, const T& min)
return value < min ? min : value;
/** brief Clamps a value to less than or equal to a specified maximum. */
template<typename T>
constexpr const T& clamp_max(const T& value, const T& max)
return value > max ? max : value;
/** brief Clamps the values held by a Spec to within a specified range.
return a new `Spec<T>`, `s`, with its minimum, typical, and maximum values clamped to the range specified
by `min` and `max`. If `spec` does not have a defined minimum then `s` has its minimum set to `min`.
If `spec` does not have a defined maximum then `s` has its maximum set to `max`. If `spec` does not
have a defined typical then `s` does not have a defined typical, either. If `spec` has a defined typical
then the defined typical for `s` is either equal to that of `spec` (if the typical is within the clamp
limits) or set to the closest clamp value (e.g. if `spec.typical() > max` then `s.typical() = max`).
throws std::logic_error if `min > max`
*/
template<typename T>
constexpr Spec<T> clamp(const Spec<T>& spec, const T& min, const T& max)
if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");
auto clamped_min = spec.clamped_min(min);
auto clamped_max = spec.clamped_max(max);
if (spec.has_typical())
auto clamped_typical = clamp(spec.typical(), min, max);
return Spec<T>(clamped_min, clamped_typical, clamped_max);
else return Spec<T>(clamped_min, clamped_max);
/** brief Determines if a value is between the limits of a Spec.
return `true` if `value` is greater than or equal to the Spec argument's minimum and less than or
equal to the Spec argument's maximum, `false` otherwise. If the Spec argument does not have a minimum
then `value` is considered greater than the Spec argument's minimum. If the Spec argument does not
have a maximum then `value` is considered less than the Spec argument's maximum. For example, if `spec`
has a minimum but no maximum then `true` is returned if `value >= spec.min()`, `false` otherwise. If
`spec` has neither a minimum nor maximum then `true` is returned for any `value`.
*/
template<typename V, typename T>
inline bool pass(const V& value, const Spec<T>& spec)
if (spec.has_min() && value < spec.min()) return false;
if (spec.has_max() && value > spec.max()) return false;
return true;
template<typename V, typename T>
inline bool fail(const V& value, const Spec<T>& spec)
return !pass(value, spec);
/** brief Calculates Spec limits based on an expected value and a % tolerance.
return a Spec with `value_type` set to the same type as `value`, a minimum equal to
`(1 - tolerance / 100) * value`, a typical equal to `value`, and a maximum equal to
`(1 + tolerance / 100) * value`. For example, for a 1% tolerance the minimum is equal to 99% of `value`
and the maximum is equal to 101% of `value`.
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance)
return Spec<V>((1 - tolerance / 100.0) * value, value, (1 + tolerance / 100.0) * value);
/** brief Calculates Spec limits based on an expected value and a % tolerance and a clamping range.
return a Spec with `value_type` set to the same type as `value`, a typical equal to `value`, a
minimum set to `(1 - tolerance / 100) * value` or `clamp_min` (whichever is greater), and a maximum
set to `(1 + tolerance / 100) * value` or `clamp_max` (whichever is less). For example, for a 1%
tolerance the minimum is equal to 99% of `value` and the maximum is equal to 101% of `value` unless
the minimum and/or maximum exceed the clamp range (in which case the minimum and/or maximum are set
to the clamp values).
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance, const V& clamp_minimum, const V& clamp_maximum)
// Min/max based on tolerance calculation
V tol_min = (1 - tolerance / 100.0) * value;
V tol_max = (1 + tolerance / 100.0) * value;
// Clamp the min/max based on the tolerance calculation to the minimum/maximum
clamp(tol_min, clamp_minimum, clamp_maximum);
clamp(tol_max, clamp_minimum, clamp_maximum);
return Spec<V>(tol_min, value, tol_max);
#endif
Here's a demo program of Spec
and the related utilities:
#include <iostream>
#include <chrono>
#include <stdexcept>
#include <vector>
#include <fstream>
#include "Spec.h"
using namespace std::chrono_literals;
template<typename T>
void print_unchecked_accesses(std::ostream& os, Spec<T> spec)
try
auto min = spec.min();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto typ = spec.typical();
catch (const bad_spec_access& err)
os << err.what() << 'n';
try
auto max = spec.max();
catch (const bad_spec_access& err)
os << err.what() << 'n';
os << 'n';
template<typename T>
void print_checked_accesses(std::ostream& os, Spec<T> spec)
if (spec.has_min()) auto min = spec.min();
else os << "non-existent minn";
if (spec.has_typical()) auto typ = spec.typical();
else os << "non-existent typicaln";
if (spec.has_max()) auto max = spec.max();
else os << "non-existent maxn";
os << 'n';
template<typename T, typename V>
void print_access_or(std::ostream& os, Spec<T> spec, V default_value)
os << "Accesses with default value " << default_value << " provided: ";
os << spec.min_or(default_value) << ", ";
os << spec.typical_or(default_value) << ", ";
os << spec.max_or(default_value) << "nn";
template<typename T, typename V>
void test_clamps(std::ostream& os, Spec<T> spec, V clamp_min, V clamp_max)
os << spec << " limits clamped to " << clamp(spec, clamp_min, clamp_max)
<< " with clamps " << clamp_min << " and " << clamp_max << 'n';
V default_value = 0;
os << "Clamping typical with default value " << default_value << ": "
<< spec.clamped_typical(clamp_min, clamp_max, default_value) << 'n';
os << "Clamping typical without a default value: ";
try
os << spec.clamped_typical(clamp_min, clamp_max);
catch (const bad_spec_access& err)
os << err.what();
os << 'n';
int main()
std::ofstream ofs("demo.txt");
double clamp_min = 0;
double clamp_max = 15;
std::vector<Spec<double> > specs =
Spec<double>(0, 5), // double-sided
Spec<double>(-5, nullspec), // min only
Spec<double>(nullspec, 298), // max only
Spec<double>(90), // typical only
Spec<double>(-79, 235, 89235), // min, typical, and max
Spec<double>(tolerance_spec<double, int>(5, 10)),
Spec<double>(tolerance_spec<double, int>(15, 10, clamp_min, clamp_max))
;
for (auto spec : specs)
ofs << "Spec: " << spec << 'n';
ofs << "Exceptions caught due to unchecked accesses:n";
print_unchecked_accesses(ofs, spec);
ofs << "Non-existent values determined from checked accesses:n";
print_checked_accesses(ofs, spec);
print_access_or(ofs, spec, -1);
test_clamps(ofs, spec, clamp_min, clamp_max);
ofs << "--------------------------------------------------------------------------------n";
ofs << "Testing equality:n";
using TimeSpec = Spec<std::chrono::microseconds>;
TimeSpec::value_type max = 5us;
TimeSpec t1(-15us, 0us, max);
TimeSpec t2 = t1;
TimeSpec t3(-10us, 0us, max); // unequal min
TimeSpec t4(-15us, 1us, max); // unequal typical
TimeSpec t5(-15us, 0us, 6us); // unequal max
std::vector<TimeSpec> timespecst1, t2, t3, t4, t5;
ofs << std::boolalpha;
for (std::size_t t = 1; t < timespecs.size(); t++)
ofs << "t1 == t" << t + 1 << "?: " << (t1 == timespecs[t]) << 'n';
ofs << 'n';
Spec<double> spec(-15, 0, 5);
ofs << "Testing pass/fail with Spec " << spec << "n";
for (auto value : -16, -15, 0, 5, 10)
ofs << value << " passes?: " << pass(value, spec) << 'n';
return 0;
The demo program outputs:
Spec: [0, 5]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent typical
Accesses with default value -1 provided: 0, -1, 5
[0, 5] limits clamped to [0, 5] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: [-5, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent typical
non-existent max
Accesses with default value -1 provided: -5, -1, -1
[-5, Inf) limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 298]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent typical spec
Non-existent values determined from checked accesses:
non-existent min
non-existent typical
Accesses with default value -1 provided: -1, -1, 298
(-Inf, 298] limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 90, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent maximum spec
Non-existent values determined from checked accesses:
non-existent min
non-existent max
Accesses with default value -1 provided: -1, 90, -1
(-Inf, 90, Inf) limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [-79, 235, 89235]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: -79, 235, 89235
[-79, 235, 89235] limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [4.5, 5, 5.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 4.5, 5, 5.5
[4.5, 5, 5.5] limits clamped to [4.5, 5, 5.5] with clamps 0 and 15
Clamping typical with default value 0: 5
Clamping typical without a default value: 5
--------------------------------------------------------------------------------
Spec: [13.5, 15, 16.5]
Exceptions caught due to unchecked accesses:
Non-existent values determined from checked accesses:
Accesses with default value -1 provided: 13.5, 15, 16.5
[13.5, 15, 16.5] limits clamped to [13.5, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Testing equality:
t1 == t2?: true
t1 == t3?: false
t1 == t4?: false
t1 == t5?: false
Testing pass/fail with Spec [-15, 0, 5]
-16 passes?: false
-15 passes?: true
0 passes?: true
5 passes?: true
10 passes?: false
I'm looking for suggestions regarding the class design, naming, etc. Also, a few specific concerns that I'd appreciate reviewers to comment on:
- I'm still learning how to use the full power of C++11 and C++14 since I was using a pre-C++11 compiler for a long time, so are there any C++11 or C++14 features I forgot to use which would improve the code?
- I'm not very comfortable with move semantics yet. Are there some optimizations I've missed because of that?
- I've provided no functions to modify the internal data of the
Spec
class (it is immutable). This simplifies the class design a bit (e.g. I don't have to check that at least one of the minimum/typical/maximum values are defined when modifying the instance -- I just have to delete the default constructor). However, in my usage it has occasionally been inconvenient to not be able to modify aSpec
instance after it's constructed (e.g. to acquire aSpec
instance with limits taken from another instance but clamped -- I have to copy it instead). Was my decision to make the class immutable a good one, or does it limit its usefulness? - It would be nice to detect if the code is being compiled by a compiler that supports
std::optional
and usestd::optional
if so, or try to fall back onboost::optional
if not. Is there a way to do this without too much difficulty?
c++ c++11 boost optional
edited Feb 7 at 18:08
200_success
123k14143401
123k14143401
asked Feb 7 at 17:27
Null
8572920
8572920
add a comment |Â
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
2
down vote
accepted
Naming:
Be consistent, and preferably avoid abbreviations. The members are minimum
, typ
and maximum
, but then there's has_min()
, has_typical()
and has_max()
, etc. It might be neater to just spell the whole thing out every time.
Constructors:
Uh... that's a lot of constructors. IMHO, it would be much neater to make the optional_type
typedef public, and have just one constructor taking the optional type for all three parameters:
Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum)
You can still pass value_type
or nullspec
directly (at least with std::optional
, I haven't checked boost::optional
), but now it's instantly obvious what's going on, because you have to define all three parameters every time:
Spec<double>(0.0, nullspec, 5.0);
There's no need to explicitly delete the default constructor, since you've defined non-default constructors.
Other:
- There's no need to static cast the results of
minimum.value_or()
inclamped_min()
, becausestd::optional::value_or()
will returnsvalue_type
already. Same with the maximum version. - You might like to check for a negative tolerance in
tolerance_spec()
(although the docs say max can be lower than min, so maybe not...). - (BUG?:) In
tolerance_spec
, you don't seem to be using the result of theclamp
operations!
Your Questions:
- There's no heap-allocated members, so there's nothing that would be faster to move than copy. The compiler will generate a move constructor for you anyway.
- If
set_foo()
member functions would be useful, there's no reason not to provide them. If an instance mustn't be altered, that's whatconst
andconst&
are for. Similar to the constructor, these can take anoptional_type
, rather than thevalue_type
.
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition ofclamp()
from taking the value as a reference to a const reference, which created a bug intolerance_spec()
. Good catch.
â Null
Feb 7 at 20:30
add a comment |Â
up vote
1
down vote
Constructors
With regard to the long list of different constructors, I think you wrote this with somebody manually writing the constructor calls. I.e. you're trying to minimize the "handwriting" effort, assuming that most of these values would come out of some file descriptions, look at how that is read and which of these is used the most. Think whether the other ones are worth keeping. Something like
Spec spec1none, 12, none;
Spec spec20.5, 12, none;
...
is also inherently more readable, as you don't have to trace back what form of the constructor was invoked. Also IIRC, at least for boost::any<T>
T implicity convert to boost::any<T>
. I.e. you can call
void func(boost::any<double> x);
via func(12.0)
this could also help cutting the amount of constructors down.
Mutablity
With regard to mutability you could consider making your class mutable but utilizing const
variable to indicate immutable values. To do this I'd suggest changing the name of your mintypicalmax
accessors to min_value
, typical_value
, etc. this lets you introduce two other accessors
boost::any<V>& min() return minimum;
const boost::any<V>& min() const return minimum;
Depending on the style that you like this might let you get rid of the has_value
functions, as you could now do
if (spec.min()) ...
The same would go for the default functions ...
spec.min().value_or(default);
yes this exposes the underlying type but sometimes (sometimes) this isn't necessarily a bad thing, epsecially when you find yourself writing a lot of functions that just wrap the interface of the internal class. It all depends on the goals.
Thoughts ...
I stared at your interface for a while, not sure if I've come to a good conclusion, the whole series of clamping call just seem very complex from the outside, but I also really couldn't find a better structure for it. We only see the interface of the class not the requirements that created this interface. Maybe the ideas from mutability could change that ...
Detection
With regard to compiler support, if you're using cmake
you can check for the existence of include files, otherwise you might have to just go and check for specific compiler versions, I think the latest releases of the big 3 clang, gcc and msvc all have std:any
implemented.
Other
Looks like the ostream
functor doesn't need to be a friend
that is a good thing.
The core guidelines suggest preferring using
over typedef
if you're just heading back to c++
it's a good habit to pick up. using
is better in dealing with templates as well.
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept twonullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.
â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferringusing
overtypedef
.
â Null
Feb 12 at 16:43
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
Naming:
Be consistent, and preferably avoid abbreviations. The members are minimum
, typ
and maximum
, but then there's has_min()
, has_typical()
and has_max()
, etc. It might be neater to just spell the whole thing out every time.
Constructors:
Uh... that's a lot of constructors. IMHO, it would be much neater to make the optional_type
typedef public, and have just one constructor taking the optional type for all three parameters:
Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum)
You can still pass value_type
or nullspec
directly (at least with std::optional
, I haven't checked boost::optional
), but now it's instantly obvious what's going on, because you have to define all three parameters every time:
Spec<double>(0.0, nullspec, 5.0);
There's no need to explicitly delete the default constructor, since you've defined non-default constructors.
Other:
- There's no need to static cast the results of
minimum.value_or()
inclamped_min()
, becausestd::optional::value_or()
will returnsvalue_type
already. Same with the maximum version. - You might like to check for a negative tolerance in
tolerance_spec()
(although the docs say max can be lower than min, so maybe not...). - (BUG?:) In
tolerance_spec
, you don't seem to be using the result of theclamp
operations!
Your Questions:
- There's no heap-allocated members, so there's nothing that would be faster to move than copy. The compiler will generate a move constructor for you anyway.
- If
set_foo()
member functions would be useful, there's no reason not to provide them. If an instance mustn't be altered, that's whatconst
andconst&
are for. Similar to the constructor, these can take anoptional_type
, rather than thevalue_type
.
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition ofclamp()
from taking the value as a reference to a const reference, which created a bug intolerance_spec()
. Good catch.
â Null
Feb 7 at 20:30
add a comment |Â
up vote
2
down vote
accepted
Naming:
Be consistent, and preferably avoid abbreviations. The members are minimum
, typ
and maximum
, but then there's has_min()
, has_typical()
and has_max()
, etc. It might be neater to just spell the whole thing out every time.
Constructors:
Uh... that's a lot of constructors. IMHO, it would be much neater to make the optional_type
typedef public, and have just one constructor taking the optional type for all three parameters:
Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum)
You can still pass value_type
or nullspec
directly (at least with std::optional
, I haven't checked boost::optional
), but now it's instantly obvious what's going on, because you have to define all three parameters every time:
Spec<double>(0.0, nullspec, 5.0);
There's no need to explicitly delete the default constructor, since you've defined non-default constructors.
Other:
- There's no need to static cast the results of
minimum.value_or()
inclamped_min()
, becausestd::optional::value_or()
will returnsvalue_type
already. Same with the maximum version. - You might like to check for a negative tolerance in
tolerance_spec()
(although the docs say max can be lower than min, so maybe not...). - (BUG?:) In
tolerance_spec
, you don't seem to be using the result of theclamp
operations!
Your Questions:
- There's no heap-allocated members, so there's nothing that would be faster to move than copy. The compiler will generate a move constructor for you anyway.
- If
set_foo()
member functions would be useful, there's no reason not to provide them. If an instance mustn't be altered, that's whatconst
andconst&
are for. Similar to the constructor, these can take anoptional_type
, rather than thevalue_type
.
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition ofclamp()
from taking the value as a reference to a const reference, which created a bug intolerance_spec()
. Good catch.
â Null
Feb 7 at 20:30
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
Naming:
Be consistent, and preferably avoid abbreviations. The members are minimum
, typ
and maximum
, but then there's has_min()
, has_typical()
and has_max()
, etc. It might be neater to just spell the whole thing out every time.
Constructors:
Uh... that's a lot of constructors. IMHO, it would be much neater to make the optional_type
typedef public, and have just one constructor taking the optional type for all three parameters:
Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum)
You can still pass value_type
or nullspec
directly (at least with std::optional
, I haven't checked boost::optional
), but now it's instantly obvious what's going on, because you have to define all three parameters every time:
Spec<double>(0.0, nullspec, 5.0);
There's no need to explicitly delete the default constructor, since you've defined non-default constructors.
Other:
- There's no need to static cast the results of
minimum.value_or()
inclamped_min()
, becausestd::optional::value_or()
will returnsvalue_type
already. Same with the maximum version. - You might like to check for a negative tolerance in
tolerance_spec()
(although the docs say max can be lower than min, so maybe not...). - (BUG?:) In
tolerance_spec
, you don't seem to be using the result of theclamp
operations!
Your Questions:
- There's no heap-allocated members, so there's nothing that would be faster to move than copy. The compiler will generate a move constructor for you anyway.
- If
set_foo()
member functions would be useful, there's no reason not to provide them. If an instance mustn't be altered, that's whatconst
andconst&
are for. Similar to the constructor, these can take anoptional_type
, rather than thevalue_type
.
Naming:
Be consistent, and preferably avoid abbreviations. The members are minimum
, typ
and maximum
, but then there's has_min()
, has_typical()
and has_max()
, etc. It might be neater to just spell the whole thing out every time.
Constructors:
Uh... that's a lot of constructors. IMHO, it would be much neater to make the optional_type
typedef public, and have just one constructor taking the optional type for all three parameters:
Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum)
You can still pass value_type
or nullspec
directly (at least with std::optional
, I haven't checked boost::optional
), but now it's instantly obvious what's going on, because you have to define all three parameters every time:
Spec<double>(0.0, nullspec, 5.0);
There's no need to explicitly delete the default constructor, since you've defined non-default constructors.
Other:
- There's no need to static cast the results of
minimum.value_or()
inclamped_min()
, becausestd::optional::value_or()
will returnsvalue_type
already. Same with the maximum version. - You might like to check for a negative tolerance in
tolerance_spec()
(although the docs say max can be lower than min, so maybe not...). - (BUG?:) In
tolerance_spec
, you don't seem to be using the result of theclamp
operations!
Your Questions:
- There's no heap-allocated members, so there's nothing that would be faster to move than copy. The compiler will generate a move constructor for you anyway.
- If
set_foo()
member functions would be useful, there's no reason not to provide them. If an instance mustn't be altered, that's whatconst
andconst&
are for. Similar to the constructor, these can take anoptional_type
, rather than thevalue_type
.
answered Feb 7 at 19:01
user673679
1,042518
1,042518
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition ofclamp()
from taking the value as a reference to a const reference, which created a bug intolerance_spec()
. Good catch.
â Null
Feb 7 at 20:30
add a comment |Â
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition ofclamp()
from taking the value as a reference to a const reference, which created a bug intolerance_spec()
. Good catch.
â Null
Feb 7 at 20:30
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
The problem with the single constructor taking three optionals is that a Spec can be constructed with no minimum/typical/maximum by calling the constructor with three optionals that don't have a value. Additionally, many specifications don't have all three parameters so it would be annoying to always have to provide nullspec arguments in those cases. While a single constructor is attractive for its simplicity, as a user of the class I've found it convenient to have additional constructors so that I don't have to use nullspec arguments in most cases.
â Null
Feb 7 at 20:14
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I explicitly deleted default constructor in part for documenting the fact that users shouldn't try to construct a Spec without at least one of the minimum/typical/maximum.
â Null
Feb 7 at 20:16
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
I'm on the fence about checking for a negative tolerance since the min is allowed to be lower than the max...but I wouldn't expect users to get a Spec with min > max that way. The reason I allow min > max is that it's possible for that to happen once I add guardbands to the class and if the guardbands are high enough to invert the min/max. It's something I'll have to think about some more.
â Null
Feb 7 at 20:21
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition of clamp()
from taking the value as a reference to a const reference, which created a bug in tolerance_spec()
. Good catch.â Null
Feb 7 at 20:30
tolerance_spec()
indeed has a bug in which it doesn't use the clamps. I think I changed the definition of clamp()
from taking the value as a reference to a const reference, which created a bug in tolerance_spec()
. Good catch.â Null
Feb 7 at 20:30
add a comment |Â
up vote
1
down vote
Constructors
With regard to the long list of different constructors, I think you wrote this with somebody manually writing the constructor calls. I.e. you're trying to minimize the "handwriting" effort, assuming that most of these values would come out of some file descriptions, look at how that is read and which of these is used the most. Think whether the other ones are worth keeping. Something like
Spec spec1none, 12, none;
Spec spec20.5, 12, none;
...
is also inherently more readable, as you don't have to trace back what form of the constructor was invoked. Also IIRC, at least for boost::any<T>
T implicity convert to boost::any<T>
. I.e. you can call
void func(boost::any<double> x);
via func(12.0)
this could also help cutting the amount of constructors down.
Mutablity
With regard to mutability you could consider making your class mutable but utilizing const
variable to indicate immutable values. To do this I'd suggest changing the name of your mintypicalmax
accessors to min_value
, typical_value
, etc. this lets you introduce two other accessors
boost::any<V>& min() return minimum;
const boost::any<V>& min() const return minimum;
Depending on the style that you like this might let you get rid of the has_value
functions, as you could now do
if (spec.min()) ...
The same would go for the default functions ...
spec.min().value_or(default);
yes this exposes the underlying type but sometimes (sometimes) this isn't necessarily a bad thing, epsecially when you find yourself writing a lot of functions that just wrap the interface of the internal class. It all depends on the goals.
Thoughts ...
I stared at your interface for a while, not sure if I've come to a good conclusion, the whole series of clamping call just seem very complex from the outside, but I also really couldn't find a better structure for it. We only see the interface of the class not the requirements that created this interface. Maybe the ideas from mutability could change that ...
Detection
With regard to compiler support, if you're using cmake
you can check for the existence of include files, otherwise you might have to just go and check for specific compiler versions, I think the latest releases of the big 3 clang, gcc and msvc all have std:any
implemented.
Other
Looks like the ostream
functor doesn't need to be a friend
that is a good thing.
The core guidelines suggest preferring using
over typedef
if you're just heading back to c++
it's a good habit to pick up. using
is better in dealing with templates as well.
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept twonullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.
â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferringusing
overtypedef
.
â Null
Feb 12 at 16:43
add a comment |Â
up vote
1
down vote
Constructors
With regard to the long list of different constructors, I think you wrote this with somebody manually writing the constructor calls. I.e. you're trying to minimize the "handwriting" effort, assuming that most of these values would come out of some file descriptions, look at how that is read and which of these is used the most. Think whether the other ones are worth keeping. Something like
Spec spec1none, 12, none;
Spec spec20.5, 12, none;
...
is also inherently more readable, as you don't have to trace back what form of the constructor was invoked. Also IIRC, at least for boost::any<T>
T implicity convert to boost::any<T>
. I.e. you can call
void func(boost::any<double> x);
via func(12.0)
this could also help cutting the amount of constructors down.
Mutablity
With regard to mutability you could consider making your class mutable but utilizing const
variable to indicate immutable values. To do this I'd suggest changing the name of your mintypicalmax
accessors to min_value
, typical_value
, etc. this lets you introduce two other accessors
boost::any<V>& min() return minimum;
const boost::any<V>& min() const return minimum;
Depending on the style that you like this might let you get rid of the has_value
functions, as you could now do
if (spec.min()) ...
The same would go for the default functions ...
spec.min().value_or(default);
yes this exposes the underlying type but sometimes (sometimes) this isn't necessarily a bad thing, epsecially when you find yourself writing a lot of functions that just wrap the interface of the internal class. It all depends on the goals.
Thoughts ...
I stared at your interface for a while, not sure if I've come to a good conclusion, the whole series of clamping call just seem very complex from the outside, but I also really couldn't find a better structure for it. We only see the interface of the class not the requirements that created this interface. Maybe the ideas from mutability could change that ...
Detection
With regard to compiler support, if you're using cmake
you can check for the existence of include files, otherwise you might have to just go and check for specific compiler versions, I think the latest releases of the big 3 clang, gcc and msvc all have std:any
implemented.
Other
Looks like the ostream
functor doesn't need to be a friend
that is a good thing.
The core guidelines suggest preferring using
over typedef
if you're just heading back to c++
it's a good habit to pick up. using
is better in dealing with templates as well.
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept twonullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.
â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferringusing
overtypedef
.
â Null
Feb 12 at 16:43
add a comment |Â
up vote
1
down vote
up vote
1
down vote
Constructors
With regard to the long list of different constructors, I think you wrote this with somebody manually writing the constructor calls. I.e. you're trying to minimize the "handwriting" effort, assuming that most of these values would come out of some file descriptions, look at how that is read and which of these is used the most. Think whether the other ones are worth keeping. Something like
Spec spec1none, 12, none;
Spec spec20.5, 12, none;
...
is also inherently more readable, as you don't have to trace back what form of the constructor was invoked. Also IIRC, at least for boost::any<T>
T implicity convert to boost::any<T>
. I.e. you can call
void func(boost::any<double> x);
via func(12.0)
this could also help cutting the amount of constructors down.
Mutablity
With regard to mutability you could consider making your class mutable but utilizing const
variable to indicate immutable values. To do this I'd suggest changing the name of your mintypicalmax
accessors to min_value
, typical_value
, etc. this lets you introduce two other accessors
boost::any<V>& min() return minimum;
const boost::any<V>& min() const return minimum;
Depending on the style that you like this might let you get rid of the has_value
functions, as you could now do
if (spec.min()) ...
The same would go for the default functions ...
spec.min().value_or(default);
yes this exposes the underlying type but sometimes (sometimes) this isn't necessarily a bad thing, epsecially when you find yourself writing a lot of functions that just wrap the interface of the internal class. It all depends on the goals.
Thoughts ...
I stared at your interface for a while, not sure if I've come to a good conclusion, the whole series of clamping call just seem very complex from the outside, but I also really couldn't find a better structure for it. We only see the interface of the class not the requirements that created this interface. Maybe the ideas from mutability could change that ...
Detection
With regard to compiler support, if you're using cmake
you can check for the existence of include files, otherwise you might have to just go and check for specific compiler versions, I think the latest releases of the big 3 clang, gcc and msvc all have std:any
implemented.
Other
Looks like the ostream
functor doesn't need to be a friend
that is a good thing.
The core guidelines suggest preferring using
over typedef
if you're just heading back to c++
it's a good habit to pick up. using
is better in dealing with templates as well.
Constructors
With regard to the long list of different constructors, I think you wrote this with somebody manually writing the constructor calls. I.e. you're trying to minimize the "handwriting" effort, assuming that most of these values would come out of some file descriptions, look at how that is read and which of these is used the most. Think whether the other ones are worth keeping. Something like
Spec spec1none, 12, none;
Spec spec20.5, 12, none;
...
is also inherently more readable, as you don't have to trace back what form of the constructor was invoked. Also IIRC, at least for boost::any<T>
T implicity convert to boost::any<T>
. I.e. you can call
void func(boost::any<double> x);
via func(12.0)
this could also help cutting the amount of constructors down.
Mutablity
With regard to mutability you could consider making your class mutable but utilizing const
variable to indicate immutable values. To do this I'd suggest changing the name of your mintypicalmax
accessors to min_value
, typical_value
, etc. this lets you introduce two other accessors
boost::any<V>& min() return minimum;
const boost::any<V>& min() const return minimum;
Depending on the style that you like this might let you get rid of the has_value
functions, as you could now do
if (spec.min()) ...
The same would go for the default functions ...
spec.min().value_or(default);
yes this exposes the underlying type but sometimes (sometimes) this isn't necessarily a bad thing, epsecially when you find yourself writing a lot of functions that just wrap the interface of the internal class. It all depends on the goals.
Thoughts ...
I stared at your interface for a while, not sure if I've come to a good conclusion, the whole series of clamping call just seem very complex from the outside, but I also really couldn't find a better structure for it. We only see the interface of the class not the requirements that created this interface. Maybe the ideas from mutability could change that ...
Detection
With regard to compiler support, if you're using cmake
you can check for the existence of include files, otherwise you might have to just go and check for specific compiler versions, I think the latest releases of the big 3 clang, gcc and msvc all have std:any
implemented.
Other
Looks like the ostream
functor doesn't need to be a friend
that is a good thing.
The core guidelines suggest preferring using
over typedef
if you're just heading back to c++
it's a good habit to pick up. using
is better in dealing with templates as well.
edited Feb 12 at 16:53
Null
8572920
8572920
answered Feb 10 at 22:46
Harald Scheirich
1,171418
1,171418
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept twonullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.
â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferringusing
overtypedef
.
â Null
Feb 12 at 16:43
add a comment |Â
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept twonullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.
â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferringusing
overtypedef
.
â Null
Feb 12 at 16:43
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept two
nullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.â Null
Feb 12 at 16:43
Since you are the second person to advocate a three-argument constructor approach I will reconsider the constructors. Perhaps as a compromise I will provide additional constructors which accept two
nullspec_type
s so that users which want to show all three arguments explicitly can do so, and users that prefer a shorter argument list when possible can use the existing constructors. My use cases involve both "handwriting" the constructor calls as well as reading values from a file (the former prefers fewer arguments, the latter would use a consistent number of arguments). Thanks for the review.â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferring
using
over typedef
.â Null
Feb 12 at 16:43
Oh, and thanks for the link to the core guidelines preferring
using
over typedef
.â Null
Feb 12 at 16:43
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f187029%2fclass-template-for-the-encapsulation-of-datasheet-specifications-using-optionals%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password