diff --git a/inkcpp/collections/restorable.h b/inkcpp/collections/restorable.h index e3a73581..173e6b5a 100644 --- a/inkcpp/collections/restorable.h +++ b/inkcpp/collections/restorable.h @@ -287,12 +287,24 @@ namespace ink::runtime::internal // Reverse find template - const ElementType* reverse_find(Predicate predicate) const + ElementType* reverse_find(Predicate predicate) { + return reverse_find_impl(predicate); + } + + template + const ElementType* reverse_find(Predicate predicate) const { + return reverse_find_impl(predicate); + } + + template + size_t size(IsNullPredicate isNull) const { if (_pos == 0) { - return nullptr; + return 0; } + size_t count = 0; + // Start at the end size_t i = _pos; do @@ -301,8 +313,8 @@ namespace ink::runtime::internal i--; // Run callback - if (predicate(_buffer[i])) - return &_buffer[i]; + if(!isNull(_buffer[i])) + count++; // Jump over saved data if (i == _save) @@ -310,18 +322,21 @@ namespace ink::runtime::internal } while (i > 0); - return nullptr; + return count; } - template - size_t size(IsNullPredicate isNull) const + protected: + // Called when we run out of space in buffer. + virtual void overflow(ElementType*& buffer, size_t& size) { throw 0; /* TODO: What to do here? Throw something more useful? */ } + + private: + template + ElementType* reverse_find_impl(Predicate predicate) const { if (_pos == 0) { - return 0; + return nullptr; } - size_t count = 0; - // Start at the end size_t i = _pos; do @@ -330,8 +345,8 @@ namespace ink::runtime::internal i--; // Run callback - if(!isNull(_buffer[i])) - count++; + if (predicate(_buffer[i])) + return &_buffer[i]; // Jump over saved data if (i == _save) @@ -339,14 +354,9 @@ namespace ink::runtime::internal } while (i > 0); - return count; + return nullptr; } - protected: - // Called when we run out of space in buffer. - virtual void overflow(ElementType*& buffer, size_t& size) { throw 0; /* TODO: What to do here? Throw something more useful? */ } - - private: // Data buffer. Collection is stored here ElementType* _buffer; @@ -360,4 +370,4 @@ namespace ink::runtime::internal size_t _jump; size_t _save; }; -} \ No newline at end of file +} diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 034f4419..c7dc5920 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -68,6 +68,79 @@ namespace ink::runtime::internal return _variables.get(name); } + value* globals_impl::get_variable(hash_t name) { + return _variables.get(name); + } + + template + auto fetch_variable( const value* v, TYPES ... types) { + return v && ((v->get_data_type() == types) || ...) + ? (v->*FN)() + : nullptr; + } + template + auto fetch_variable(value* v, TYPES ... types) { + return v && ((v->get_data_type() == types) || ...) + ? (v->*FN)() + : nullptr; + } + + const uint32_t* globals_impl::get_uint(hash_t name) const { + return fetch_variable(get_variable(name), data_type::uint32); + } + bool globals_impl::set_uint(hash_t name, uint32_t val) { + uint32_t* p = fetch_variable(get_variable(name), data_type::uint32); + if (p == nullptr) { return false; } + *p = val; + return true; + } + + const int32_t* globals_impl::get_int(hash_t name) const { + return fetch_variable(get_variable(name), data_type::int32); + } + bool globals_impl::set_int(hash_t name, int32_t val) { + int32_t* p = fetch_variable(get_variable(name), data_type::int32); + if (p == nullptr) { return false; } + *p = val; + return true; + } + + const float* globals_impl::get_float(hash_t name) const { + return fetch_variable(get_variable(name), data_type::float32); + } + bool globals_impl::set_float(hash_t name, float val) { + float* p = fetch_variable(get_variable(name), data_type::float32); + if (p == nullptr) { return false; } + *p = val; + return true; + } + + const char * const * globals_impl::get_str(hash_t name) const { + const value* v = get_variable(name); + if (v->type() != value_type::string) { return nullptr; } + return v->as_str_ptr(_strings); + } + bool globals_impl::set_str(hash_t name, const char* val) { + value* v = get_variable(name); + if (v->type() == value_type::string) + { + size_t size = 0; + char* ptr; + for(const char*i = val; *i; ++i) { ++size; } + char* new_string = strings().create(size + 1); + strings().mark_used(new_string); + ptr = new_string; + for(const char* i = val; *i; ++i) { + *ptr++ = *i; + } + internal::data d; + d.set_string(new_string, true); + *v = internal::value(d); + return true; + } + return false; + } + void globals_impl::initialize_globals(runner_impl* run) { // If no way to move there, then there are no globals. @@ -118,4 +191,4 @@ namespace ink::runtime::internal _visit_counts.forget(); _variables.forget(); } -} \ No newline at end of file +} diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index ee362e48..bf6ce785 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -19,7 +19,18 @@ namespace ink::runtime::internal globals_impl(const story_impl*); virtual ~globals_impl() { } - virtual void dummy() override { } + protected: + const uint32_t* get_uint(hash_t name) const override; + bool set_uint(hash_t name, uint32_t value) override; + + const int32_t* get_int(hash_t name) const override; + bool set_int(hash_t name, int32_t value) override; + + const float* get_float(hash_t name) const override; + bool set_float(hash_t name, float value) override; + + const char * const * get_str(hash_t name) const override; + bool set_str(hash_t name, const char* value) override; public: // Records a visit to a container @@ -37,6 +48,7 @@ namespace ink::runtime::internal // gets a global variable const value* get_variable(hash_t name) const; + value* get_variable(hash_t name); // checks if globals are initialized bool are_globals_initialized() const { return _globals_initialized; } @@ -46,7 +58,7 @@ namespace ink::runtime::internal // gets the allocated string table inline string_table& strings() { return _strings; } - + // run garbage collection void gc(); @@ -54,10 +66,11 @@ namespace ink::runtime::internal void save(); void restore(); void forget(); + private: // Store the number of containers. This is the length of most of our lists const uint32_t _num_containers; - + // Visit count array internal::allocated_restorable_array _visit_counts; @@ -75,7 +88,7 @@ namespace ink::runtime::internal runner_entry* _runners_start; // Allocated string table (shared by all runners using this global store) - string_table _strings; + mutable string_table _strings; // Global variables (TODO: Max 50?) // Implemented as a stack (slow lookup) because it has save/restore functionality. @@ -83,4 +96,4 @@ namespace ink::runtime::internal internal::stack<50> _variables; bool _globals_initialized; }; -} \ No newline at end of file +} diff --git a/inkcpp/include/globals.h b/inkcpp/include/globals.h index 8262eb53..231fc368 100644 --- a/inkcpp/include/globals.h +++ b/inkcpp/include/globals.h @@ -4,6 +4,9 @@ namespace ink::runtime { + class globals_interface; + namespace internal { class globals_impl;} + /** * Represents a global store to be shared amongst ink runners. * Stores global variable values, visit counts, turn counts, etc. @@ -11,8 +14,88 @@ namespace ink::runtime class globals_interface { public: - // No public interface yet - virtual void dummy() = 0; + + /** + * @brief Access global variable of Ink runner. + * @param name name of variable, as defined in InkScript + * @tparam T c++ type of variable + * @return nullopt if variable won't exist or type won't match + */ + template + optional get(const char* name) const { + static_assert( + internal::always_false::value, + "Requested Type is not supported"); + } + + /** + * @brief Write new value in global store. + * @param name name of variable, as defined in InkScript + * @tparam T c++ type of variable + * @return true on success + */ + template + bool set(const char* name, const T& val) { + static_assert( + internal::always_false::value, + "Requested Type is not supported"); + return false; + } + virtual ~globals_interface() = default; + + protected: + virtual const uint32_t* get_uint(hash_t name) const = 0; + virtual bool set_uint(hash_t name, uint32_t val) = 0; + virtual const int32_t* get_int(hash_t name) const = 0; + virtual bool set_int(hash_t name, int32_t val) = 0; + virtual const float* get_float(hash_t name) const = 0; + virtual bool set_float(hash_t name, float val) = 0; + virtual const char* const * get_str(hash_t name) const = 0; + virtual bool set_str(hash_t name, const char* val) = 0; }; + + template<> + inline optional globals_interface::get(const char* name) const { + const uint32_t* p = get_uint(hash_string(name)); + if (p) { return {*p}; } + return {nullopt}; + } + template<> + inline bool globals_interface::set(const char* name, const uint32_t& val) { + return set_uint(hash_string(name), val); + } + + template<> + inline optional globals_interface::get(const char* name) const { + const int32_t* p = get_int(hash_string(name)); + if (p) { return {*p}; } + return {nullopt}; + } + template<> + inline bool globals_interface::set(const char* name, const int32_t& val) { + return set_int(hash_string(name), val); + } + + template<> + inline optional globals_interface::get(const char* name) const { + const float* p = get_float(hash_string(name)); + if (p) { return {*p}; } + return {nullopt}; + } + template<> + inline bool globals_interface::set(const char* name, const float& val) { + return set_float(hash_string(name), val); + } + + template<> + inline optionalglobals_interface::get(const char* name) const { + const char * const * p = get_str(hash_string(name)); + if (p) { return {*p}; } + return {nullopt}; + } + template<> + inline bool globals_interface::set(const char* name, const char * const & val) { + return set_str(hash_string(name), val); + } } diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 4dd02de1..54742edc 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -1,56 +1,21 @@ #include "output.h" #include "string_table.h" +#include +#include "string_utils.h" #ifdef INK_ENABLE_STL #include #endif -#include -#include -#include - namespace ink { namespace runtime { namespace internal { - template - int toStr(char * buffer, size_t size, T value) { - static_assert(!std::is_same::value, "Type not supported for conversion!"); - return EINVAL; - } - - // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 - template<> - int toStr(char * buffer, size_t size, int value) { -#ifdef WIN32 - return _itoa_s(value, buffer, size, 10); -#else - if ( buffer == nullptr || size < 1 ){ return EINVAL; } - int res = snprintf(buffer, size, "%d", value); - if (res > 0 && res < size) { return 0; } - return EINVAL; -#endif - } - - // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/gcvt-s?view=msvc-160 - template<> - int toStr(char * buffer, size_t size, float value) { -#ifdef WIN32 - return _gcvt_s(buffer, size, value, 11); -#else - if ( buffer == nullptr || size < 1 ) { return EINVAL; } - int res = snprintf(buffer, size, "%f.10", value); - if (res > 0 && res < size) { return 0; } - return EINVAL; -#endif - } basic_stream::basic_stream(data* buffer, size_t len) : _data(buffer), _max(len), _size(0), _save(~0) - { - - } + {} void basic_stream::append(const data& in) { @@ -62,7 +27,7 @@ namespace ink if (_data[_size - 1].type == data_type::func_start) return; } - + // Ignore leading newlines if (in.type == data_type::newline && _size == 0) return; @@ -114,7 +79,7 @@ namespace ink template inline void write_char(OUT& output, char c) { - static_assert(! std::is_same::value, "Invalid output type"); + static_assert(always_false::value, "Invalid output type"); } template<> @@ -421,10 +386,10 @@ namespace ink switch (_data[i].type) { case data_type::int32: - length += 11; + length += decimal_digits(_data[i].integer_value); break; case data_type::float32: - length += 11; // ??? + length += decimal_digits(_data[i].float_value); break; case data_type::string_table_pointer: case data_type::allocated_string_pointer: diff --git a/inkcpp/stack.cpp b/inkcpp/stack.cpp index 5e7b0e1b..fac96cd5 100644 --- a/inkcpp/stack.cpp +++ b/inkcpp/stack.cpp @@ -24,12 +24,8 @@ namespace ink::runtime::internal *existing = val; } - const value* basic_stack::get(hash_t name) const - { - // Find whatever comes first: a matching entry or a stack frame entry - thread_t skip = ~0; - uint32_t jumping = 0; - const entry* found = base::reverse_find([name, &skip, &jumping](entry& e) { + auto reverse_find_predicat_constructor(hash_t name, thread_t& skip, uint32_t& jumping) { + return [name, &skip, &jumping](entry& e) { // Jumping if (jumping > 0) { jumping--; @@ -66,7 +62,31 @@ namespace ink::runtime::internal } return e.name == name || e.name == InvalidHash; - }); + }; + } + + const value* basic_stack::get(hash_t name) const { + // Find whatever comes first: a matching entry or a stack frame entry + thread_t skip = ~0; + uint32_t jumping = 0; + const entry* found = base::reverse_find(reverse_find_predicat_constructor(name, skip, jumping)); + + // If nothing found, no value + if (found == nullptr) + return nullptr; + + // If we found something of that name, return the value + if (found->name == name) + return &found->data; + + // Otherwise, nothing in this stack frame + return nullptr; + } + value* basic_stack::get(hash_t name) { + // Find whatever comes first: a matching entry or a stack frame entry + thread_t skip = ~0; + uint32_t jumping = 0; + entry* found = base::reverse_find(reverse_find_predicat_constructor(name, skip, jumping)); // If nothing found, no value if (found == nullptr) @@ -470,4 +490,4 @@ namespace ink::runtime::internal value none = value(x); base::forget([&none](value& elem) { elem = none; }); } -} \ No newline at end of file +} diff --git a/inkcpp/stack.h b/inkcpp/stack.h index 6108061c..29292bb9 100644 --- a/inkcpp/stack.h +++ b/inkcpp/stack.h @@ -36,6 +36,7 @@ namespace ink // Gets an existing value, or nullptr const value* get(hash_t name) const; + value* get(hash_t name); // pushes a new frame onto the stack void push_frame(offset_t return_to, frame_type type); @@ -134,4 +135,4 @@ namespace ink }; } } -} \ No newline at end of file +} diff --git a/inkcpp/string_utils.h b/inkcpp/string_utils.h new file mode 100644 index 00000000..cd6956fc --- /dev/null +++ b/inkcpp/string_utils.h @@ -0,0 +1,68 @@ +#pragma once + +#include "system.h" + +#include + +namespace ink::runtime::internal { + + // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 + inline int toStr(char * buffer, size_t size, uint32_t value) { +#ifdef WIN32 + return _itoa_s(value, buffer, size, 10); +#else + if ( buffer == nullptr || size < 1 ){ return EINVAL; } + int res = snprintf(buffer, size, "%d", value); + if (res > 0 && res < size) { return 0; } + return EINVAL; +#endif + } + + // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/itoa-s-itow-s?view=msvc-160 + inline int toStr(char * buffer, size_t size, int32_t value) { +#ifdef WIN32 + return _itoa_s(value, buffer, size, 10); +#else + if ( buffer == nullptr || size < 1 ){ return EINVAL; } + int res = snprintf(buffer, size, "%d", value); + if (res > 0 && res < size) { return 0; } + return EINVAL; +#endif + } + + // error behavior from: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/gcvt-s?view=msvc-160 + inline int toStr(char * buffer, size_t size, float value) { +#ifdef WIN32 + return _gcvt_s(buffer, size, value, 7); // number of significant digits +#else + if ( buffer == nullptr || size < 1 ) { return EINVAL; } + int res = snprintf(buffer, size, "%f.7", value); + if (res > 0 && res < size) { return 0; } + return EINVAL; +#endif + } + inline size_t strlen(const char* str) { + size_t len = 0; + for(const char* c = str; *c; ++c) { + ++len; + } + return len; + } + + // return a upper bound for the string representation of the number + inline constexpr size_t decimal_digits(uint32_t number) { + size_t length = 1; + while(number /= 10) { ++length; } + return length; + } + + inline constexpr size_t decimal_digits(int32_t number) { + size_t length = number < 0 ? 2 : 1; + while(number /= 10) { ++length; } + return length; + } + + inline constexpr size_t decimal_digits(float number) { + return 16; + } +} diff --git a/inkcpp/value.cpp b/inkcpp/value.cpp index ba8099ba..6da899c5 100644 --- a/inkcpp/value.cpp +++ b/inkcpp/value.cpp @@ -1,6 +1,7 @@ #include "value.h" #include "output.h" #include "string_table.h" +#include "string_utils.h" namespace ink { @@ -324,7 +325,59 @@ namespace ink } } - + void value::finalize_string(string_table& table) const { + constexpr size_t MaxSize = 256; // max size for no string element + char buffer[VALUE_DATA_LENGTH][MaxSize]; + const char* strs[VALUE_DATA_LENGTH]; + char null = 0; + + size_t len = 0; + for (int i = 0; i < VALUE_DATA_LENGTH; ++i) { + switch(_data[i].type) { + case data_type::float32: + strs[i] = buffer[i]; + toStr(buffer[i], MaxSize, _data[i].float_value); + break; + case data_type::uint32: + strs[i] = buffer[i]; + toStr(buffer[i], MaxSize, _data[i].uint_value); + break; + case data_type::int32: + strs[i] = buffer[i]; + toStr(buffer[i], MaxSize, _data[i].integer_value); + break; + case data_type::string_table_pointer: + case data_type::allocated_string_pointer: + strs[i] = _data[i].string_val; + break; + default: strs[i] = &null; + } + _data[i].set_none(); + len += strlen(strs[i]); + } + char* str = table.create(len+1); + table.mark_used(str); + + char* ptr = str; + for (int i = 0; i < VALUE_DATA_LENGTH; ++i) { + for(const char* c = strs[i]; *c; ++c){ + *ptr++ = *c; + } + } + *ptr = 0; + _data[0].set_string(str, true); + } + + const char* value::as_str(string_table& table) const { + finalize_string(table); + return _first.string_val; + } + + const char * const * value::as_str_ptr(string_table& table) const { + finalize_string(table); + return &_first.string_val; + } + bool value::compare_string(const value& left, const value& right) { // convert fields to string representation and start comparison // when the end of one field is reached, the other still has diff --git a/inkcpp/value.h b/inkcpp/value.h index b6f087d8..0bc32ae0 100644 --- a/inkcpp/value.h +++ b/inkcpp/value.h @@ -96,10 +96,17 @@ namespace ink // == Getters == int as_int() const { return _first.integer_value; } + int* as_int_ptr() { return &_first.integer_value; } + const int* as_int_ptr() const { return &_first.integer_value; } float as_float() const { return _first.float_value; } + float* as_float_ptr() { return &_first.float_value; } + const float* as_float_ptr() const { return &_first.float_value; } uint32_t as_divert() const { return _first.uint_value; } uint32_t as_thread_id() const { return _first.uint_value; } - // TODO: String access? + uint32_t* as_uint_ptr() { return &_first.uint_value; } + const uint32_t* as_uint_ptr() const { return &_first.uint_value; } + const char* as_str(string_table&) const; + const char* const * as_str_ptr(string_table&) const; template T get() const { static_assert(always_false::value, "Type not supported by value class"); } @@ -145,6 +152,10 @@ namespace ink * @brief compare if the string representation of values are equal. */ static bool compare_string(const value& left, const value& right); + /** + * @brief compress string values to one data field + */ + void finalize_string(string_table&) const; private: // Maximum sequential data a value can have @@ -153,16 +164,16 @@ namespace ink union { // Quick access struct - struct + struct { - data _first; + data _first; data _second; }; - + // Data array - data _data[VALUE_DATA_LENGTH]; + mutable data _data[VALUE_DATA_LENGTH]; }; - + }; // == Binary Operators == diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 0faa9301..27a01340 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -5,8 +5,17 @@ add_executable(inkcpp_test catch.hpp Main.cpp Callstack.cpp Restorable.cpp Value.cpp + Globals.cpp ) -target_link_libraries(inkcpp_test PUBLIC inkcpp) +target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler) add_test(NAME UnitTests COMMAND $) +set (source "${CMAKE_CURRENT_SOURCE_DIR}/ink") +set (destination "${CMAKE_CURRENT_BINARY_DIR}/ink") +add_custom_command( + TARGET inkcpp_test POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination} + DEPENDS ${destination} + COMMENT "symbolic link resources folder from ${source} => ${destination}" +) diff --git a/inkcpp_test/Globals.cpp b/inkcpp_test/Globals.cpp new file mode 100644 index 00000000..15eb86d7 --- /dev/null +++ b/inkcpp_test/Globals.cpp @@ -0,0 +1,76 @@ +#include "catch.hpp" +#include "../inkcpp_cl/test.cpp" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("run story with global variable", "[global variables]") +{ + GIVEN ("a story with global variables") + { + inklecate("ink/GlobalStory.ink", "GlobalsStory.tmp"); + ink::compiler::run("GlobalsStory.tmp", "GlobalsStory.bin"); + auto ink = story::from_file("GlobalsStory.bin"); + globals globStore = ink->new_globals(); + runner thread = ink->new_runner(globStore); + + WHEN( "just runs") + { + THEN("variables should contain values as in inkScript") + { + REQUIRE(thread->getall() == "My name is Jean Passepartout, but my friend's call me Jackie. I'm 23 years old.\nFoo:23\n"); + REQUIRE(*globStore->get("age") == 23); + REQUIRE(*globStore->get("friendly_name_of_player") == std::string{"Jackie"}); + } + } + WHEN ("edit number") + { + bool resi + = globStore->set("age", 30); + bool resc + = globStore->set("friendly_name_of_player", "Freddy"); + THEN("execution should success") + { + REQUIRE(resi == true); + REQUIRE(resc == true); + } + THEN("variable should contain new value") + { + REQUIRE(thread->getall() == "My name is Jean Passepartout, but my friend's call me Freddy. I'm 30 years old.\nFoo:30\n"); + REQUIRE(*globStore->get("age") == 30); + REQUIRE(*globStore->get("friendly_name_of_player") == std::string{"Freddy"}); + } + WHEN ("something added to string") + { + // concat in GlobalsStory.ink + thread->getall(); + THEN("get should return the whole string") + { + REQUIRE(*globStore->get("concat") == std::string{"Foo:30"}); + } + } + } + WHEN ("name or type not exist") + { + auto wrongType = globStore->get("age"); + auto notExistingName = globStore->get("foo"); + THEN("should return nullptr") + { + REQUIRE(wrongType.has_value() == false); + REQUIRE(notExistingName.has_value() == false); + } + + bool rest = globStore->set("age", 3); + bool resn = globStore->set("foo", 3); + THEN("should return false") + { + REQUIRE(rest == false); + REQUIRE(resn == false); + } + } + } +} diff --git a/inkcpp_test/ink/GlobalStory.ink b/inkcpp_test/ink/GlobalStory.ink new file mode 100644 index 00000000..ace5657e --- /dev/null +++ b/inkcpp_test/ink/GlobalStory.ink @@ -0,0 +1,9 @@ +// from official ink tutorial + +VAR friendly_name_of_player = "Jackie" +VAR age = 23 +VAR concat = "Foo:" + +My name is Jean Passepartout, but my friend's call me {friendly_name_of_player}. I'm {age} years old. +~ concat += age +{concat} diff --git a/shared/public/system.h b/shared/public/system.h index 20197f22..0a7d6a5c 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -11,6 +11,7 @@ #ifdef INK_ENABLE_STL #include #include +#include #endif namespace ink @@ -121,6 +122,47 @@ namespace ink template struct always_false { static constexpr bool value = false; }; } + +#ifdef INK_ENABLE_STL + template + using optional = std::optional; + constexpr std::nullopt_t nullopt = std::nullopt; +#else + struct nullopt_t{}; + constexpr nullopt_t nullopt; + + template + class optional { + public: + optional() {} + optional(nullopt_t) {} + optional(T&& val) _has_value{true}, _value{std::forward(val)}{} + optional(const T& val) _has_value{true}, _value{val}{} + + const T& operator*() const { return _value; } + T& operator*() { return _value; } + const T* operator->() const { return &_value; } + T* operator->() { return &_value; } + + constexpr bool has_value() const { return _has_value; } + constexpr T& value() { check(); return _value; } + constexpr const T& value() const { check(); return _value; } + constexpr operator bool() const { return has_value(); } + template + constexpr T value_or(U&& u) const { + return _has_value ? _value : static_cast(std::forward(u)); + } + private: + void check() const { + if ( ! _has_value) { + throw ink_exception("Can't access empty optional!"); + } + } + + bool _has_value = false; + T _value; + }; +#endif } // Platform specific defines //