From 152beff483dd010d93fd6cf07793bba27e44c390 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Mon, 13 Jun 2022 12:47:37 +1000 Subject: [PATCH] bool: add tribool class --- CMakeLists.txt | 3 ++ bool.cpp | 9 +++++ bool.hpp | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ test/bool.cpp | 50 ++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 bool.cpp create mode 100644 bool.hpp create mode 100644 test/bool.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c89e2dd..fd39941d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/bool.cpp b/bool.cpp new file mode 100644 index 00000000..5a45ca17 --- /dev/null +++ b/bool.cpp @@ -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 + */ + +#include "bool.hpp" \ No newline at end of file diff --git a/bool.hpp b/bool.hpp new file mode 100644 index 00000000..01a05c04 --- /dev/null +++ b/bool.hpp @@ -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 + */ + +#pragma once + +#include +#include + + +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; + }; +} \ No newline at end of file diff --git a/test/bool.cpp b/test/bool.cpp new file mode 100644 index 00000000..0898eb8f --- /dev/null +++ b/test/bool.cpp @@ -0,0 +1,50 @@ +#include +#include + +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 (); +} \ No newline at end of file