├── qt-maybe.pri ├── TestMaybe.h ├── qt-maybe.pro ├── LICENSE ├── TestMaybe.cpp ├── README.md ├── Maybe.h └── Either.h /qt-maybe.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD/ 2 | HEADERS += $$PWD/Either.h $$PWD/Maybe.h 3 | 4 | -------------------------------------------------------------------------------- /TestMaybe.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class TestMaybe : public QObject 6 | { 7 | Q_OBJECT 8 | 9 | private Q_SLOTS: 10 | 11 | // Maybe 12 | void testMaybe(); 13 | void testMaybeCast(); 14 | void testMaybePtr(); 15 | void testMaybeSize(); 16 | 17 | // Either 18 | void testEither(); 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /qt-maybe.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (2.01a) Mon Jan 9 14:16:42 2012 3 | ###################################################################### 4 | 5 | TEMPLATE = app 6 | TARGET = TestMaybe 7 | DEPENDPATH += . 8 | INCLUDEPATH += . 9 | QT += testlib 10 | 11 | # Input 12 | HEADERS += Either.h Maybe.h TestMaybe.h 13 | SOURCES += TestMaybe.cpp 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Robert Knight 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /TestMaybe.cpp: -------------------------------------------------------------------------------- 1 | #include "TestMaybe.h" 2 | 3 | #include "Maybe.h" 4 | 5 | #include 6 | 7 | Q_DECLARE_METATYPE(int*); 8 | 9 | void TestMaybe::testMaybe() 10 | { 11 | Maybe tryOk = just(42); 12 | Maybe tryFail = nothing(); 13 | Maybe tryZero = just(0); 14 | 15 | QVERIFY(tryOk); 16 | QVERIFY(!tryFail); 17 | QVERIFY(tryZero); 18 | QCOMPARE(tryZero.value(),0); 19 | QCOMPARE(tryOk.value(),42); 20 | } 21 | 22 | void TestMaybe::testMaybeCast() 23 | { 24 | Maybe fromCStr = just("hello world"); 25 | Maybe fromNothing = nothing(); 26 | 27 | QVERIFY(!fromNothing); 28 | QCOMPARE(fromCStr.value(),QString("hello world")); 29 | 30 | Maybe maybeShort = just(42); 31 | QVERIFY(maybeShort); 32 | QCOMPARE(maybeShort.value(),'*'); 33 | } 34 | 35 | struct BigStruct 36 | { 37 | double array[42]; 38 | }; 39 | 40 | void TestMaybe::testMaybeSize() 41 | { 42 | Maybe maybeInt; 43 | Maybe maybeDouble; 44 | Maybe maybeIntPtr; 45 | Maybe maybeQString; 46 | Maybe maybeBig; 47 | 48 | QCOMPARE(sizeof(maybeInt),12U); 49 | QCOMPARE(sizeof(maybeDouble),12U); 50 | QCOMPARE(sizeof(maybeIntPtr),12U); 51 | QCOMPARE(sizeof(maybeQString),12U); 52 | QCOMPARE(sizeof(maybeBig),12U); 53 | } 54 | 55 | void TestMaybe::testMaybePtr() 56 | { 57 | int foo = 42; 58 | int* bar = 0; 59 | 60 | Maybe nullPtr = just(bar); 61 | Maybe nullPtr2 = nothing(); 62 | Maybe notNullPtr = just(&foo); 63 | 64 | QVERIFY(!nullPtr); 65 | QVERIFY(!nullPtr2); 66 | QVERIFY(notNullPtr); 67 | } 68 | 69 | void TestMaybe::testEither() 70 | { 71 | Either eitherInt = some(42); 72 | Either eitherBool = some(true); 73 | 74 | QVERIFY(eitherInt.is()); 75 | QVERIFY(eitherInt.is1st()); 76 | QVERIFY(!eitherInt.is()); 77 | QVERIFY(!eitherInt.is2nd()); 78 | QCOMPARE(eitherInt.as(),42); 79 | QCOMPARE(eitherInt.as1st(),42); 80 | 81 | QVERIFY(eitherBool.is()); 82 | QVERIFY(eitherBool.is2nd()); 83 | QVERIFY(!eitherBool.is()); 84 | QVERIFY(!eitherBool.is1st()); 85 | QCOMPARE(eitherBool.as(),true); 86 | QCOMPARE(eitherBool.as2nd(),true); 87 | } 88 | 89 | QTEST_APPLESS_MAIN(TestMaybe) 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Option/Sum Types for Qt Projects 2 | 3 | A set of simple(ish) C++ templates which implement [sum](http://en.wikipedia.org/wiki/Sum_type) and [option](http://en.wikipedia.org/wiki/Option_type) 4 | types. They serve a similar purpose to `boost::variant` and `boost::optional` 5 | but are implemented on top of Qt's QVariant container. 6 | 7 | **Update (2017-09-23): A `variant` class has been added to the C++ standard library for C++17 ([std::variant](http://en.cppreference.com/w/cpp/utility/variant)) and you can find [polyfills](https://github.com/mpark/variant) for compilers that don't ship with it yet. Consider using that instead of this code in your project.** 8 | 9 | ## Templates 10 | 11 | ### Either 12 | 13 | `Either` represents either a value of type `T1` or 14 | a value of type `T2`. You can think of it as a QVariant which is restricted 15 | to being either a `T1` or `T2` rather than any type which QVariant supports. 16 | 17 | `Either` instances can be constructed directly from an instance 18 | of `T1` or `T2` or by using the `some(T)` function which returns a type that 19 | can be implicitly cast to any `Either` type that has `T` as one of its 20 | types. 21 | 22 | Simple example: 23 | 24 | // parse a value which might either be a 25 | // string or an integer 26 | 27 | Either value = parseValue(data); 28 | if (value.is1st()) { 29 | qDebug() << "Read string " << value.as1st(); 30 | } else { 31 | qDebug() << "Read integer " << value.as2nd(); 32 | } 33 | 34 | ### Maybe 35 | 36 | `Maybe` represents either a value of type T or no value. You can think of it 37 | as a QVariant which is restricted to either being a T or a null type. 38 | 39 | `Maybe` instances are constructed using the `just(T)` or `nothing()` 40 | functions. 41 | 42 | `just(T)` handles pointers specially. If passed a null pointer it returns 43 | a `Maybe` representing no value. This is useful when working with existing code 44 | which uses a null pointer to represent no value. 45 | 46 | Simple example: 47 | 48 | // try to read the size of a file, which may fail if 49 | // the file is not readable 50 | 51 | Maybe size = fileSize(path); 52 | if (size) { 53 | std::cout << "file size is " << size.value() << " bytes" << std::endl; 54 | } else { 55 | std::cout << "could not read file size" << std::endl; 56 | } 57 | 58 | ## Examples 59 | 60 | See `TestMaybe.cpp` for unit tests which show example usage of the `Maybe` and 61 | `Either` templates. 62 | 63 | ## Performance 64 | 65 | The templates use `QVariant` as the underlying storage and the same performance considerations apply. A QVariant in Qt 4 is 12 bytes in size on most platforms - 8 bytes for the data itself or a pointer to the data, plus 4 bytes for the type tag and flags. Pointers and primitive types of 8 bytes in size or less are stored within the QVariant. Larger types are stored in heap-allocated storage. 66 | 67 | ## Exceptions 68 | 69 | Unlike `boost::variant`, no attempt is made to [handle exceptions](http://www.boost.org/doc/libs/1_48_0/doc/html/variant/design.html#variant.design.never-empty) that may be thrown when constructing or copying types. The state of a `Maybe` or `Either` after a failed copy constructor call is undefined. 70 | -------------------------------------------------------------------------------- /Maybe.h: -------------------------------------------------------------------------------- 1 | #ifndef QT_MAYBE_H 2 | #define QT_MAYBE_H 3 | 4 | #include "Either.h" 5 | 6 | /** A simple option type implementation which represents the result 7 | * of an operation which returned either a valid result or nothing. 8 | * 9 | * Maybe is built on top of Either, using one type to 10 | * hold the result (of type T) and the other to represent no result. 11 | * 12 | * Since Maybe is built on Either, it uses QVariant for the underlying 13 | * value storage and has the same size. 14 | * 15 | * Maybe instances can be constructed using the just(T) and 16 | * nothing() helper functions. 17 | */ 18 | template 19 | class Maybe 20 | { 21 | public: 22 | /** Construct a Maybe instance representing no 23 | * value. 24 | */ 25 | Maybe() 26 | : m_value(false) 27 | {} 28 | 29 | /** Returns true if this Maybe has a value. */ 30 | operator bool() const { 31 | return m_value.template is(); 32 | } 33 | 34 | /** Returns the value from this Maybe. Asserts 35 | * in debug builds if there is no value. 36 | */ 37 | T value() const { 38 | return m_value.template as(); 39 | } 40 | 41 | // helper method for the non-member just() function, 42 | // which external helpers should use instead 43 | template 44 | static Maybe just(const U& value); 45 | 46 | private: 47 | // use the just() function to create a new 48 | // Maybe instance with a value 49 | Maybe(const T& value) 50 | : m_value(value) 51 | {} 52 | 53 | Either m_value; 54 | }; 55 | 56 | template 57 | template 58 | Maybe Maybe::just(const U& value) 59 | { 60 | return Maybe(value); 61 | } 62 | 63 | // helper type used by nothing() 64 | struct NothingType 65 | { 66 | template 67 | operator Maybe() const { 68 | return Maybe(); 69 | } 70 | }; 71 | 72 | // helper type used by just() 73 | template 74 | struct JustType 75 | { 76 | JustType(const T& _value) 77 | : value(_value) 78 | {} 79 | 80 | template 81 | operator Maybe() const { 82 | return Maybe::just(value); 83 | } 84 | 85 | const T& value; 86 | }; 87 | 88 | // helper type specialized for pointer types 89 | template 90 | struct JustType 91 | { 92 | JustType(T* _value) 93 | : value(_value) 94 | {} 95 | 96 | operator Maybe() const { 97 | // just(T*) returns a no-value Maybe if the pointer 98 | // is null 99 | return value ? Maybe::just(value) : Maybe(); 100 | } 101 | 102 | T* value; 103 | }; 104 | 105 | /** Returns a type which can be implicitly cast to any Maybe type, 106 | * representing a function which returned no value. 107 | */ 108 | inline NothingType nothing() 109 | { 110 | return NothingType(); 111 | } 112 | 113 | /** Returns a type which can be implicitly cast to a Maybe type 114 | * representing a function which did return a value of @p value. 115 | * 116 | * just() is specialized for pointer types to return nothing() 117 | * if the pointer is null. 118 | */ 119 | template 120 | inline JustType just(const T& value) { 121 | return JustType(value); 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /Either.h: -------------------------------------------------------------------------------- 1 | #ifndef QT_EITHER_H 2 | #define QT_EITHER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | struct EitherHelper 10 | { 11 | }; 12 | 13 | template 14 | struct EitherHelper 15 | { 16 | EitherHelper(const QVariant& _value) 17 | : value(_value) 18 | {} 19 | 20 | bool is() const 21 | { 22 | return value.userType() == qMetaTypeId(); 23 | } 24 | 25 | T1 as() const { 26 | return value.value(); 27 | } 28 | 29 | const QVariant& value; 30 | }; 31 | 32 | template 33 | struct EitherHelper 34 | { 35 | EitherHelper(const QVariant& _value) 36 | : value(_value) 37 | {} 38 | 39 | bool is() const { 40 | return value.userType() == qMetaTypeId(); 41 | } 42 | 43 | T2 as() const { 44 | return value.value(); 45 | } 46 | 47 | const QVariant& value; 48 | }; 49 | 50 | /** A sum type which represents either an instance of T1 or 51 | * an instance of T2. 52 | * 53 | * Either uses QVariant for the underlying storage, so the 54 | * same performance considerations apply. An Either instance 55 | * has the same size as a QVariant (12 bytes) regardless of the 56 | * types used. 57 | * 58 | * A convenient way to create an Either instance is 59 | * the some(T) function which returns a type that can be implicitly 60 | * cast to any Either<> type where T1 or T2 is T. 61 | */ 62 | template 63 | class Either 64 | { 65 | public: 66 | Either(const T1& t1) 67 | : m_value(QVariant::fromValue(t1)) 68 | { 69 | } 70 | 71 | Either(const T2& t2) 72 | : m_value(QVariant::fromValue(t2)) 73 | { 74 | } 75 | 76 | template 77 | bool is() const; 78 | 79 | template 80 | T as() const; 81 | 82 | bool is1st() const; 83 | bool is2nd() const; 84 | 85 | T1 as1st() const; 86 | T2 as2nd() const; 87 | 88 | private: 89 | template 90 | EitherHelper is_t() const; 91 | 92 | QVariant m_value; 93 | }; 94 | 95 | template 96 | template 97 | EitherHelper Either::is_t() const 98 | { 99 | return EitherHelper(m_value); 100 | } 101 | 102 | template 103 | template 104 | bool Either::is() const 105 | { 106 | return is_t().is(); 107 | } 108 | 109 | template 110 | template 111 | T Either::as() const 112 | { 113 | Q_ASSERT_X(is(),"Either::as()","Tried to extract the wrong type from an Either instance"); 114 | return is_t().as(); 115 | } 116 | 117 | template 118 | bool Either::is1st() const 119 | { 120 | return is(); 121 | } 122 | 123 | template 124 | bool Either::is2nd() const 125 | { 126 | return is(); 127 | } 128 | 129 | template 130 | T1 Either::as1st() const 131 | { 132 | return as(); 133 | } 134 | 135 | template 136 | T2 Either::as2nd() const 137 | { 138 | return as(); 139 | } 140 | 141 | template 142 | struct MakeEither 143 | { 144 | MakeEither(const T1& _value) 145 | : value(_value) 146 | {} 147 | 148 | template 149 | operator Either() 150 | { 151 | return Either(value); 152 | } 153 | 154 | template 155 | operator Either() 156 | { 157 | return Either(value); 158 | } 159 | 160 | const T1& value; 161 | }; 162 | 163 | template 164 | inline MakeEither some(const T1& value) 165 | { 166 | return MakeEither(value); 167 | } 168 | 169 | #endif 170 | --------------------------------------------------------------------------------