bool: add tribool class

This commit is contained in:
Danny Robson 2022-06-13 12:47:37 +10:00
parent cdaa5ebd41
commit 152beff483
4 changed files with 165 additions and 0 deletions

View File

@ -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
View 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
View 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
View 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 ();
}