Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

std::optional #2780

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/std_optional.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added `core.stdcpp.optional`.

Added `core.stdcpp.optional`, which links against C++ `std::optional`
1 change: 1 addition & 0 deletions mak/COPY
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions mak/DOCS
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions mak/SRCS
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion setmscver.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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
201 changes: 201 additions & 0 deletions src/core/stdcpp/optional.d
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion test/stdcpp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
53 changes: 53 additions & 0 deletions test/stdcpp/src/optional.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <optional>

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<int>, const std::optional<int>&,
std::optional<void*>, const std::optional<void*>&,
std::optional<Complex>, const std::optional<Complex>&);

int callC_val(bool set, std::optional<int> a1, const std::optional<int>& a2,
std::optional<void*> a3, const std::optional<void*>& a4,
std::optional<Complex> a5, const std::optional<Complex>& 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);
}
97 changes: 97 additions & 0 deletions test/stdcpp/src/optional_test.d
Original file line number Diff line number Diff line change
@@ -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;
}