bool: add tribool class
This commit is contained in:
parent
cdaa5ebd41
commit
152beff483
@ -281,6 +281,8 @@ list (
|
||||
bezier.hpp
|
||||
bitwise.cpp
|
||||
bitwise.hpp
|
||||
bool.cpp
|
||||
bool.hpp
|
||||
buffer/simple.cpp
|
||||
buffer/simple.hpp
|
||||
buffer/traits.hpp
|
||||
@ -711,6 +713,7 @@ if (TESTS)
|
||||
backtrace
|
||||
bezier
|
||||
bitwise
|
||||
bool
|
||||
buffer/simple
|
||||
cmdopt
|
||||
cmdopt2
|
||||
|
9
bool.cpp
Normal file
9
bool.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#include "bool.hpp"
|
103
bool.hpp
Normal file
103
bool.hpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2022, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace cruft {
|
||||
/// A tristate boolean object. ie, contains true, false, and "don't care".
|
||||
///
|
||||
/// This is helpful when folding multiple objects together (eg, in
|
||||
/// configuration files).
|
||||
///
|
||||
/// The logical operators do not operate on the underlying logical value.
|
||||
/// They only serve to overwrite DONTCARE states.
|
||||
/// ie, FALSE | TRUE == FALSE, whereas DONTCARE | TRUE = TRUE.
|
||||
class tribool {
|
||||
public:
|
||||
constexpr tribool (bool _value)
|
||||
: m_value (_value ? value::TRUE : value::FALSE)
|
||||
{ ; }
|
||||
|
||||
constexpr tribool (std::nullptr_t)
|
||||
: m_value (value::DONTCARE)
|
||||
{ ; }
|
||||
|
||||
/// Return true if the object contains a concrete value
|
||||
constexpr bool has (void) const
|
||||
{
|
||||
return m_value != value::DONTCARE;
|
||||
}
|
||||
|
||||
/// Return the concrete value, else throw an exception
|
||||
constexpr bool get (void) const
|
||||
{
|
||||
if (m_value == value::DONTCARE) [[unlikely]]
|
||||
throw std::logic_error ("tribool has not value");
|
||||
return bool (m_value);
|
||||
}
|
||||
|
||||
/// Return the concrete value, else return the provided default
|
||||
constexpr bool get (bool fallback) const
|
||||
{
|
||||
return m_value == value::DONTCARE ? fallback : bool (m_value);
|
||||
}
|
||||
|
||||
/// If the underlying value is "don't care" then update it with the
|
||||
/// provided value. Otherwise no change.
|
||||
constexpr tribool& ensure (bool fallback)&
|
||||
{
|
||||
if (m_value == value::DONTCARE)
|
||||
m_value = fallback ? value::TRUE : value::FALSE;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr tribool
|
||||
ensure (bool fallback) &&
|
||||
{
|
||||
return tribool (get (fallback));
|
||||
}
|
||||
|
||||
|
||||
constexpr tribool
|
||||
operator| (tribool const &rhs) const noexcept
|
||||
{
|
||||
if (m_value == value::DONTCARE)
|
||||
return rhs;
|
||||
else
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr tribool&
|
||||
operator|= (tribool const &rhs)& noexcept
|
||||
{
|
||||
if (m_value == value::DONTCARE)
|
||||
m_value = rhs.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator== (tribool const&) const noexcept = default;
|
||||
constexpr bool operator!= (tribool const&) const noexcept = default;
|
||||
|
||||
private:
|
||||
// Representation is:
|
||||
// -1 == don't care
|
||||
// 0 == false
|
||||
// 1 == true
|
||||
//
|
||||
// This lets us test for validity with -ve, otherwise cast to bool
|
||||
//
|
||||
// Use an `enum class` so that the values aren't implicitly converted
|
||||
// to bool when constructing a tribool (it's caught me out a few times
|
||||
// already).
|
||||
enum class value { DONTCARE = -1, TRUE = 1, FALSE = 0 } m_value;
|
||||
};
|
||||
}
|
50
test/bool.cpp
Normal file
50
test/bool.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include <cruft/util/tap.hpp>
|
||||
#include <cruft/util/bool.hpp>
|
||||
|
||||
using cruft::tribool;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
static char const*
|
||||
tribool_to_str (tribool const &val)
|
||||
{
|
||||
if (!val.has ())
|
||||
return "?";
|
||||
|
||||
return val.get () ? "t" : "f";
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
|
||||
{
|
||||
// Exhausting `or` testing. Because why not.
|
||||
static constexpr struct {
|
||||
tribool a;
|
||||
tribool b;
|
||||
tribool res;
|
||||
} TESTS[] = {
|
||||
{ .a = tribool (false), .b = tribool (false), .res = tribool (false), },
|
||||
{ .a = tribool (false), .b = tribool (true), .res = tribool (false), },
|
||||
{ .a = tribool (true), .b = tribool (false), .res = tribool (true), },
|
||||
{ .a = tribool (true), .b = tribool (true), .res = tribool (true) },
|
||||
|
||||
{ .a = tribool (nullptr), .b = tribool (false), .res = tribool (false), },
|
||||
{ .a = tribool (nullptr), .b = tribool (true), .res = tribool (true), },
|
||||
|
||||
{ .a = tribool (true), .b = tribool (nullptr), .res = tribool (true), },
|
||||
{ .a = tribool (false), .b = tribool (nullptr), .res = tribool (false), },
|
||||
|
||||
{ .a = tribool (nullptr), .b = tribool (nullptr), .res = tribool (nullptr), },
|
||||
};
|
||||
|
||||
for (auto const &[a, b, res]: TESTS)
|
||||
tap.expect ((a | b) == res, "{} | {}", tribool_to_str (a), tribool_to_str (b));
|
||||
}
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user