diff --git a/changelog/std_optional.dd b/changelog/std_optional.dd new file mode 100644 index 0000000000..2d167077d2 --- /dev/null +++ b/changelog/std_optional.dd @@ -0,0 +1,3 @@ +Added `core.stdcpp.optional`. + +Added `core.stdcpp.optional`, which links against C++ `std::optional` diff --git a/mak/COPY b/mak/COPY index 1f8cc3ad4e..fb79e658d3 100644 --- a/mak/COPY +++ b/mak/COPY @@ -112,6 +112,7 @@ COPY=\ $(IMPDIR)\core\stdcpp\exception.d \ $(IMPDIR)\core\stdcpp\memory.d \ $(IMPDIR)\core\stdcpp\new_.d \ + $(IMPDIR)\core\stdcpp\optional.d \ $(IMPDIR)\core\stdcpp\string.d \ $(IMPDIR)\core\stdcpp\string_view.d \ $(IMPDIR)\core\stdcpp\type_traits.d \ diff --git a/mak/DOCS b/mak/DOCS index 40afd9f4e7..402fe67304 100644 --- a/mak/DOCS +++ b/mak/DOCS @@ -107,6 +107,7 @@ DOCS=\ $(DOCDIR)\core_stdcpp_xutility.html \ $(DOCDIR)\core_stdcpp_new_.html \ $(DOCDIR)\core_stdcpp_string.html \ + $(DOCDIR)\core_stdcpp_optional.html \ $(DOCDIR)\core_stdcpp_vector.html \ \ $(DOCDIR)\core_sync_event.html \ diff --git a/mak/SRCS b/mak/SRCS index f4da0d6863..37e47a5c44 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -109,6 +109,7 @@ SRCS=\ src\core\stdcpp\exception.d \ src\core\stdcpp\memory.d \ src\core\stdcpp\new_.d \ + src\core\stdcpp\optional.d \ src\core\stdcpp\string.d \ src\core\stdcpp\string_view.d \ src\core\stdcpp\typeinfo.d \ diff --git a/setmscver.bat b/setmscver.bat index 3a38e1a47e..bbe72f4010 100644 --- a/setmscver.bat +++ b/setmscver.bat @@ -9,5 +9,5 @@ if exist dflags.txt del /q dflags.txt if exist add_tests.txt del /q add_tests.txt if %_MSC_VER% GTR 1900 echo /std:c++17 > cflags.txt if %_MSC_VER% GTR 1900 echo -extern-std=c++17 > dflags.txt -if %_MSC_VER% GTR 1900 echo string_view > add_tests.txt +if %_MSC_VER% GTR 1900 echo optional string_view > add_tests.txt del ver.c ver_raw.txt diff --git a/src/core/stdcpp/optional.d b/src/core/stdcpp/optional.d new file mode 100644 index 0000000000..2cf9973ed7 --- /dev/null +++ b/src/core/stdcpp/optional.d @@ -0,0 +1,201 @@ +/** + * D header file for interaction with C++ std::optional. + * + * Copyright: Copyright (c) 2018 D Language Foundation + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Manu Evans + * Source: $(DRUNTIMESRC core/stdcpp/optional.d) + */ + +module core.stdcpp.optional; + +import core.stdcpp.exception : exception; + +version (CppRuntime_DigitalMars) +{ + pragma(msg, "std::optional not supported by DMC"); +} +version (CppRuntime_Clang) +{ + private alias AliasSeq(Args...) = Args; + private enum StdNamespace = AliasSeq!("std", "__1"); +} +else +{ + private enum StdNamespace = "std"; +} + + +extern(C++, "std") +{ + /// + class bad_optional_access : exception + { + @nogc: + /// + this(const(char)* message = "bad exception") nothrow { super(message); } + } +} + + +extern(C++, (StdNamespace)): + +/// +struct nullopt_t {} + +/// +enum nullopt_t nullopt = nullopt_t(); + +/// +struct in_place_t {} + +/// +enum in_place_t in_place = in_place_t(); + +/** + * D language counterpart to C++ std::optional. + * + * C++ reference: $(LINK2 https://en.cppreference.com/w/cpp/utility/optional) + */ +extern(C++, class) struct optional(T) +{ + static assert(!is(Unqual!T == nullopt_t), "T in optional!T cannot be nullopt_t (N4659 23.6.2 [optional.syn]/1)."); + static assert(!is(Unqual!T == in_place_t), "T in optional!T cannot be in_place_t (N4659 23.6.2 [optional.syn]/1)."); + static assert(!__traits(hasMember, T, "__xpostblit"), "T in optional!T may not have a postblit `this(this)` constructor. Use copy constructor instead."); +// static assert(is_reference_v<_Ty> || is_object_v<_Ty>, "T in optional!T must be an object type (N4659 23.6.3 [optional.optional]/3)."); +// static assert(is_destructible_v<_Ty> && !is_array_v<_Ty>, "T in optional!T must satisfy the requirements of Destructible (N4659 23.6.3 [optional.optional]/3)."); + + import core.internal.traits : AliasSeq, Unqual, hasElaborateDestructor, hasElaborateCopyConstructor, hasElaborateDestructor; + import core.lifetime : forward, moveEmplace, core_emplace = emplace; + +extern(D): +pragma(inline, true): + + /// + this(nullopt_t) pure nothrow @nogc @safe + { + } + + /// + this(Args...)(in_place_t, auto ref Args args) + { + static if (Args.length == 1 && is(Unqual!(Args[0]) == T)) + moveEmplace(args[0], _value); + else + core_emplace(&_value, forward!args); + _engaged = true; + } + + static if (hasElaborateCopyConstructor!T) + { + /// + this(ref return scope inout(optional) rhs) inout + { + _engaged = rhs._engaged; + if (rhs._engaged) + core_emplace(cast(T*)&_value, rhs._value); + } + } + + static if (hasElaborateDestructor!T) + { + /// + ~this() + { + if (_engaged) + destroy!false(_value); + } + } + + /// + void opAssign(nullopt_t) + { + reset(); + } + + /// + void opAssign()(auto ref optional!T rhs) + { + if (rhs._engaged) + opAssign(forward!rhs._value); + else + reset(); + } + + /// + void opAssign()(auto ref T rhs) + { + if (_engaged) + _value = forward!rhs; + else + { + core_emplace(&_value, forward!rhs); + _engaged = true; + } + } + + /// + bool opCast(T : bool)() const pure nothrow @nogc @safe + { + return has_value(); + } + + /// + void reset() + { + static if (hasElaborateDestructor!T) + { + if (_engaged) + { + destroy!false(_value); + _engaged = false; + } + } + else + _engaged = false; + } + + /// + ref T emplace(Args...)(auto ref Args args) + { + reset(); + core_emplace(&_value, forward!args); + _engaged = true; + return _value; + } + + /// + bool has_value() const pure nothrow @nogc @safe + { + return _engaged; + } + + // TODO: return by-val (move _value) if `this` is an rvalue... (auto ref return?) + /// + ref inout(T) value() inout return pure @trusted // @nogc //(DIP1008) + in (_engaged == true) + { + // TODO: support C++ exceptions? +// if (!_engaged) +// throw new bad_optional_access(); + return _value; + } + + // TODO: return by-val (move _value) if `this` is an rvalue... (auto ref return?) + /// + ref inout(T) value_or(scope return ref inout(T) or) inout return pure nothrow @nogc @trusted + { + return _engaged ? _value : or; + } + +private: + // amazingly, MSVC, Clang and GCC all share the same struct! + union + { + ubyte _dummy = 0; + Unqual!T _value = void; + } + bool _engaged = false; +} diff --git a/test/stdcpp/Makefile b/test/stdcpp/Makefile index e8fd1ec964..1f7d3cd198 100644 --- a/test/stdcpp/Makefile +++ b/test/stdcpp/Makefile @@ -4,7 +4,7 @@ HASCPP17:=`echo wow | $(CXX) -std=c++17 -E -xc++ - > /dev/null 2>&1 && echo yes` TESTS:=allocator new utility TESTS11:=array -TESTS17:=string_view +TESTS17:=optional string_view OLDABITESTS:= ifeq (osx,$(OS)) diff --git a/test/stdcpp/src/optional.cpp b/test/stdcpp/src/optional.cpp new file mode 100644 index 0000000000..25e52d0fd9 --- /dev/null +++ b/test/stdcpp/src/optional.cpp @@ -0,0 +1,53 @@ +#include + +extern int opt_refCount; + +struct Complex +{ + bool valid = false; + + int buffer[16] = { 10 }; + + Complex() = delete; + Complex(const Complex& rh) + { + valid = rh.valid; + if (rh.valid) + { + ++opt_refCount; + for (int i = 0; i < 16; ++i) + buffer[i] = rh.buffer[i]; + } + } + ~Complex() + { + if (valid) + --opt_refCount; + } +}; + +int fromC_val(bool, std::optional, const std::optional&, + std::optional, const std::optional&, + std::optional, const std::optional&); + +int callC_val(bool set, std::optional a1, const std::optional& a2, + std::optional a3, const std::optional& a4, + std::optional a5, const std::optional& a6) +{ + if (set) + { + if (!a1 || a1.value() != 10) return 1; + if (!a2 || a2.value() != 10) return 1; + if (!a3 || a3.value() != (void*)0x1234) return 1; + if (!a4 || a4.value() != (void*)0x1234) return 1; + if (!a5 || a5.value().buffer[0] != 20 || a5.value().buffer[15] != 20) return 1; + if (!a6 || a6.value().buffer[0] != 20 || a6.value().buffer[15] != 20) return 1; + } + else + { + if (a1 || a2 || a3 || a4 || a5 || a6) + return 1; + } + + return fromC_val(set, a1, a2, a3, a4, a5, a6); +} diff --git a/test/stdcpp/src/optional_test.d b/test/stdcpp/src/optional_test.d new file mode 100644 index 0000000000..19ad1adb85 --- /dev/null +++ b/test/stdcpp/src/optional_test.d @@ -0,0 +1,97 @@ +import core.stdcpp.optional; + +unittest +{ + optional!int o1; + optional!int o2 = nullopt; + auto o3 = optional!int(in_place, 10); + assert(!o1 && o2.has_value == false && o3 && o3.value == 10); + o1 = 20; + assert(o1 && o1.value == 20); + o1 = nullopt; + assert(!o1); + int temp = 30; + assert(o1.value_or(temp) == 30); + + optional!(void*) o4; + auto o5 = optional!(void*)(in_place, cast(void*)0x1234); + assert(!o4 && o5 && o5.value == cast(void*)0x1234); + o4 = o5; + o5 = null; + assert(o5.value == null); + o5 = o4; + o5.reset(); + assert(!o5); + + { + optional!Complex o6; + auto o7 = optional!Complex(in_place, Complex(20)); + assert(!o6 && o7 && o7.value.buffer[0] == 20 && o7.value.buffer[$-1] == 20); + optional!Complex o8 = o6; + assert(!o8); + optional!Complex o9 = o7; + assert(o9 && o9.value.buffer[0] == 20 && o9.value.buffer[$-1] == 20); + o9 = o6; + assert(!o9); + o6 = o7; + assert(o6 && o6.value.buffer[0] == 20 && o6.value.buffer[$-1] == 20); + o7.reset(); + assert(!o7); + + assert(callC_val(false, o1, o1, o5, o5, o7, o7) == 0); + assert(callC_val(true, o3, o3, o4, o4, o6, o6) == 0); + } + assert(opt_refCount == 0); +} + +extern(C++): + +__gshared int opt_refCount = 0; + +struct Complex +{ + bool valid = false; + int[16] buffer; + this(int val) + { + valid = true; + buffer[] = val; + ++opt_refCount; + } + this(ref inout(Complex) rhs) inout + { + valid = rhs.valid; + if (rhs.valid) + { + buffer[] = rhs.buffer[]; ++opt_refCount; + } + } + ~this() + { + if (valid) + --opt_refCount; + } +} + +int callC_val(bool, optional!int, ref const(optional!int), optional!(void*), ref const(optional!(void*)), optional!Complex, ref const(optional!Complex)); + +int fromC_val(bool set, optional!int a1, ref const(optional!int) a2, + optional!(void*) a3, ref const(optional!(void*)) a4, + optional!Complex a5, ref const(optional!Complex) a6) +{ + if (set) + { + assert(a1 && a1.value == 10); + assert(a2 && a2.value == 10); + assert(a3 && a3.value == cast(void*)0x1234); + assert(a4 && a4.value == cast(void*)0x1234); + assert(a5 && a5.value.buffer[0] == 20 && a5.value.buffer[$-1] == 20); + assert(a6 && a6.value.buffer[0] == 20 && a6.value.buffer[$-1] == 20); + } + else + { + assert(!a1 && !a2 && !a3 && !a4 && !a5 && !a6); + } + + return 0; +}