From 2d7dc8c58320fc004e80eb7a213134c32e8a9f79 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Thu, 25 Jun 2015 15:46:53 +0200 Subject: [PATCH] Added first version for an either type --- either.h | 95 +++++++++++++++++++ test/EitherTest.cpp | 221 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 either.h create mode 100644 test/EitherTest.cpp diff --git a/either.h b/either.h new file mode 100644 index 00000000..c1672111 --- /dev/null +++ b/either.h @@ -0,0 +1,95 @@ +#pragma once +#ifndef MESSMER_CPP_UTILS_EITHER_H +#define MESSMER_CPP_UTILS_EITHER_H + +namespace cpputils { + + template + class Either final { + public: + Either(const Left &left): _side(Side::left) { + new(&_left)Left(left); + } + Either(Left &&left): _side(Side::left) { + new(&_left)Left(std::move(left)); + } + Either(const Right &right): _side(Side::right) { + new(&_right)Right(right); + } + Either(Right &&right): _side(Side::right) { + new(&_right)Right(std::move(right)); + } + Either(const Either &rhs): _side(rhs._side) { + if(_side == Side::left) { + new(&_left)Left(rhs._left); + } else { + new(&_right)Right(rhs._right); + } + } + Either(Either &&rhs): _side(rhs._side) { + if(_side == Side::left) { + new(&_left)Left(std::move(rhs._left)); + } else { + new(&_right)Right(std::move(rhs._right)); + } + } + //TODO Test copy assignment operator + //TODO Copy assignment operator + //TODO Test destruction after copy assignment + + //TODO Test move assignment operator + //TODO Move assignment operator + //TODO Test destruction after move assignment + + //TODO Test operator==/operator!= + //TODO operator==/operator!= + + //TODO Test operator<< + //TODO operator<<(ostream) + + ~Either() { + if (_side == Side::left) { + _left.~Left(); + } else { + _right.~Right(); + } + } + + bool is_left() const { + return _side == Side::left; + } + + bool is_right() const { + return _side == Side::right; + } + + //TODO Also offer a safe version of getting left/right (exceptions? nullptr?) + const Left &left() const { + return _left; + } + + const Right &right() const { + return _right; + } + + //TODO Test const and non-const left()/right() + Left &left() { + return const_cast(const_cast*>(this)->left()); + } + Right &right() { + return const_cast(const_cast*>(this)->right()); + } + private: + union { + Left _left; + Right _right; + }; + enum class Side : unsigned char {left, right} _side; + }; + + //TODO Test make_either<> + //TODO make_either<> +} + + +#endif diff --git a/test/EitherTest.cpp b/test/EitherTest.cpp new file mode 100644 index 00000000..fc4ab7c4 --- /dev/null +++ b/test/EitherTest.cpp @@ -0,0 +1,221 @@ +#include +#include +#include "../either.h" +#include "../macros.h" + +class OnlyMoveable { +public: + OnlyMoveable(int value_): value(value_) {} + OnlyMoveable(OnlyMoveable &&source): value(source.value) {source.value = -1;} + int value; +private: + DISALLOW_COPY_AND_ASSIGN(OnlyMoveable); +}; + +using std::string; +using namespace cpputils; +using ::testing::Test; + +class EitherTest: public Test { +public: + template + void EXPECT_IS_LEFT(const Either &val) { + EXPECT_TRUE(val.is_left()); + EXPECT_FALSE(val.is_right()); + } + template + void EXPECT_IS_RIGHT(const Either &val) { + EXPECT_FALSE(val.is_left()); + EXPECT_TRUE(val.is_right()); + } + template + void EXPECT_LEFT_IS(const Expected &expected, Either &value) { + EXPECT_IS_LEFT(value); + EXPECT_EQ(expected, value.left()); + const Either &const_value = value; + EXPECT_EQ(expected, const_value.left()); + } + template + void EXPECT_RIGHT_IS(const Expected &expected, Either &value) { + EXPECT_IS_RIGHT(value); + EXPECT_EQ(expected, value.right()); + const Either &const_value = value; + EXPECT_EQ(expected, const_value.right()); + } +}; + +TEST_F(EitherTest, LeftCanBeConstructed) { + Either val = 3; + UNUSED(val); +} + +TEST_F(EitherTest, RightCanBeConstructed) { + Either val = string("string"); + UNUSED(val); +} + +TEST_F(EitherTest, IsLeft) { + Either val = 3; + EXPECT_IS_LEFT(val); +} + +TEST_F(EitherTest, IsRight) { + Either val = string("string"); + EXPECT_IS_RIGHT(val); +} + +TEST_F(EitherTest, LeftIsStored) { + Either val = 3; + EXPECT_LEFT_IS(3, val); +} + +TEST_F(EitherTest, RightIsStored) { + Either val = string("string"); + EXPECT_RIGHT_IS("string", val); +} + +TEST_F(EitherTest, LeftCanBeMoveContructed) { + Either val = OnlyMoveable(1); + UNUSED(val); +} + +TEST_F(EitherTest, RightCanBeMoveContructed) { + Either val = OnlyMoveable(1); + UNUSED(val); +} + +TEST_F(EitherTest, IsLeftWhenMoveContructed) { + Either val = OnlyMoveable(1); + EXPECT_IS_LEFT(val); +} + +TEST_F(EitherTest, IsRightWhenMoveContructed) { + Either val = OnlyMoveable(1); + EXPECT_IS_RIGHT(val); +} + +TEST_F(EitherTest, LeftIsStoredWhenMoveContructed) { + Either val = OnlyMoveable(2); + EXPECT_EQ(2, val.left().value); +} + +TEST_F(EitherTest, RightIsStoredWhenMoveContructed) { + Either val = OnlyMoveable(3); + EXPECT_EQ(3, val.right().value); +} + +TEST_F(EitherTest, LeftCanBeCopied) { + Either val = string("string"); + Either val2 = val; + EXPECT_LEFT_IS("string", val2); +} + +TEST_F(EitherTest, CopyingLeftDoesntChangeSource) { + Either val = string("string"); + Either val2 = val; + EXPECT_LEFT_IS("string", val); +} + +TEST_F(EitherTest, RightCanBeCopied) { + Either val = string("string"); + Either val2 = val; + EXPECT_RIGHT_IS("string", val2); +} + +TEST_F(EitherTest, CopyingRightDoesntChangeSource) { + Either val = string("string"); + Either val2 = val; + EXPECT_RIGHT_IS("string", val); +} + +TEST_F(EitherTest, LeftCanBeMoved) { + Either val = OnlyMoveable(5); + Either val2 = std::move(val); + EXPECT_IS_LEFT(val2); + EXPECT_EQ(5, val2.left().value); +} + +TEST_F(EitherTest, RightCanBeMoved) { + Either val = OnlyMoveable(5); + Either val2 = std::move(val); + EXPECT_IS_RIGHT(val2); + EXPECT_EQ(5, val2.right().value); +} + +class DestructorCallback { +public: + MOCK_CONST_METHOD0(call, void()); +}; +class ClassWithDestructorCallback { +public: + ClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) { } + + ~ClassWithDestructorCallback() { + _destructorCallback->call(); + } + +private: + const DestructorCallback *_destructorCallback; +}; +class OnlyMoveableClassWithDestructorCallback { +public: + OnlyMoveableClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) { } + OnlyMoveableClassWithDestructorCallback(OnlyMoveableClassWithDestructorCallback &&source): _destructorCallback(source._destructorCallback) {} + + ~OnlyMoveableClassWithDestructorCallback() { + _destructorCallback->call(); + } + +private: + DISALLOW_COPY_AND_ASSIGN(OnlyMoveableClassWithDestructorCallback); + const DestructorCallback *_destructorCallback; +}; + +class EitherTest_Destructor: public EitherTest { +public: + DestructorCallback destructorCallback; + + void EXPECT_DESTRUCTOR_CALLED(int times = 1) { + EXPECT_CALL(destructorCallback, call()).Times(times); + } +}; + +TEST_F(EitherTest_Destructor, LeftDestructorIsCalled) { + ClassWithDestructorCallback temp(&destructorCallback); + Either var = temp; + EXPECT_DESTRUCTOR_CALLED(2); //Once for the temp object, once when the either class destructs +} + +TEST_F(EitherTest_Destructor, RightDestructorIsCalled) { + ClassWithDestructorCallback temp(&destructorCallback); + Either var = temp; + EXPECT_DESTRUCTOR_CALLED(2); //Once for the temp object, once when the either class destructs +} + +TEST_F(EitherTest_Destructor, LeftDestructorIsCalledAfterCopying) { + ClassWithDestructorCallback temp(&destructorCallback); + Either var1 = temp; + Either var2 = var1; + EXPECT_DESTRUCTOR_CALLED(3); //Once for the temp object, once for var1 and once for var2 +} + +TEST_F(EitherTest_Destructor, RightDestructorIsCalledAfterCopying) { + ClassWithDestructorCallback temp(&destructorCallback); + Either var1 = temp; + Either var2 = var1; + EXPECT_DESTRUCTOR_CALLED(3); //Once for the temp object, once for var1 and once for var2 +} + +TEST_F(EitherTest_Destructor, LeftDestructorIsCalledAfterMoving) { + OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); + Either var1 = std::move(temp); + Either var2 = std::move(var1); + EXPECT_DESTRUCTOR_CALLED(3); //Once for the temp object, once for var1 and once for var2 +} + +TEST_F(EitherTest_Destructor, RightDestructorIsCalledAfterMoving) { + OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); + Either var1 = std::move(temp); + Either var2 = std::move(var1); + EXPECT_DESTRUCTOR_CALLED(3); //Once for the temp object, once for var1 and once for var2 +} \ No newline at end of file