Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1864cf473 | |||
8db1851c5c | |||
12de700706 | |||
78a8afdf75 | |||
3c996d7c57 | |||
2a68048fc1 | |||
907f7a4e61 | |||
670328c047 | |||
7fe69ccc5c | |||
26c3532e28 | |||
75ce854192 | |||
9e16d84f9e |
@ -7,9 +7,9 @@ os:
|
|||||||
language: d
|
language: d
|
||||||
|
|
||||||
d:
|
d:
|
||||||
- dmd-2.077.0
|
- dmd-2.078.0
|
||||||
|
- dmd-2.077.1
|
||||||
- dmd-2.076.1
|
- dmd-2.076.1
|
||||||
- dmd-2.075.1
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
matrix:
|
matrix:
|
||||||
@ -22,7 +22,7 @@ addons:
|
|||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- if [ "$PS1" = '(dmd-2.077.0)' ]; then
|
- if [ "$PS1" = '(dmd-2.078.0)' ]; then
|
||||||
export UNITTEST="unittest-cov";
|
export UNITTEST="unittest-cov";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
13
README.md
13
README.md
@ -12,8 +12,8 @@ Tanya is a general purpose library for D programming language.
|
|||||||
Its aim is to simplify the manual memory management in D and to provide a
|
Its aim is to simplify the manual memory management in D and to provide a
|
||||||
guarantee with @nogc attribute that there are no hidden allocations on the
|
guarantee with @nogc attribute that there are no hidden allocations on the
|
||||||
Garbage Collector heap. Everything in the library is usable in @nogc code.
|
Garbage Collector heap. Everything in the library is usable in @nogc code.
|
||||||
Tanya extends Phobos functionality and provides alternative implementations for
|
Tanya provides data structures and utilities to facilitate painless systems
|
||||||
data structures and utilities that depend on the Garbage Collector in Phobos.
|
programming in D.
|
||||||
|
|
||||||
* [API Documentation](https://docs.caraus.io/tanya)
|
* [API Documentation](https://docs.caraus.io/tanya)
|
||||||
* [Contribution guidelines](CONTRIBUTING.md)
|
* [Contribution guidelines](CONTRIBUTING.md)
|
||||||
@ -149,9 +149,9 @@ There are more containers in the `tanya.container` package.
|
|||||||
|
|
||||||
| DMD | GCC |
|
| DMD | GCC |
|
||||||
|:-------:|:--------------:|
|
|:-------:|:--------------:|
|
||||||
| 2.077.0 | *gdc-5* branch |
|
| 2.078.0 | *gdc-5* branch |
|
||||||
|
| 2.077.1 | |
|
||||||
| 2.076.1 | |
|
| 2.076.1 | |
|
||||||
| 2.075.1 | |
|
|
||||||
|
|
||||||
### Current status
|
### Current status
|
||||||
|
|
||||||
@ -161,13 +161,10 @@ Following modules are under development:
|
|||||||
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| BitArray | bitvector | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|
| BitArray | bitvector | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|
||||||
| TLS | crypto | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
|
| TLS | crypto | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
|
||||||
| File IO | io | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
|
|
||||||
|
|
||||||
### Release management
|
### Release management
|
||||||
|
|
||||||
3-week release cycle.
|
Deprecated features are removed after one release that includes these deprecations.
|
||||||
|
|
||||||
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
|
|
||||||
|
|
||||||
## Further characteristics
|
## Further characteristics
|
||||||
|
|
||||||
|
16
appveyor.yml
16
appveyor.yml
@ -4,10 +4,16 @@ os: Visual Studio 2015
|
|||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- DC: dmd
|
- DC: dmd
|
||||||
DVersion: 2.077.0
|
DVersion: 2.078.0
|
||||||
arch: x64
|
arch: x64
|
||||||
- DC: dmd
|
- DC: dmd
|
||||||
DVersion: 2.077.0
|
DVersion: 2.078.0
|
||||||
|
arch: x86
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.077.1
|
||||||
|
arch: x64
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.077.1
|
||||||
arch: x86
|
arch: x86
|
||||||
- DC: dmd
|
- DC: dmd
|
||||||
DVersion: 2.076.1
|
DVersion: 2.076.1
|
||||||
@ -15,12 +21,6 @@ environment:
|
|||||||
- DC: dmd
|
- DC: dmd
|
||||||
DVersion: 2.076.1
|
DVersion: 2.076.1
|
||||||
arch: x86
|
arch: x86
|
||||||
- DC: dmd
|
|
||||||
DVersion: 2.075.1
|
|
||||||
arch: x64
|
|
||||||
- DC: dmd
|
|
||||||
DVersion: 2.075.1
|
|
||||||
arch: x86
|
|
||||||
|
|
||||||
skip_tags: true
|
skip_tags: true
|
||||||
|
|
||||||
|
7
dub.json
7
dub.json
@ -12,7 +12,12 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "library",
|
"name": "library",
|
||||||
"targetType": "library",
|
"targetType": "staticLibrary",
|
||||||
|
"versions": ["TanyaPhobos"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dynamic",
|
||||||
|
"targetType": "dynamicLibrary",
|
||||||
"versions": ["TanyaPhobos"]
|
"versions": ["TanyaPhobos"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -264,7 +264,8 @@ if (is(Unqual!E == char))
|
|||||||
body
|
body
|
||||||
{
|
{
|
||||||
dchar chr;
|
dchar chr;
|
||||||
ubyte units, mask;
|
ubyte units;
|
||||||
|
int mask;
|
||||||
const(char)* it = this.begin;
|
const(char)* it = this.begin;
|
||||||
|
|
||||||
if (*it & 0x80)
|
if (*it & 0x80)
|
||||||
|
@ -323,9 +323,9 @@ private HP raise2Power10(const HP value, int power)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given a float value, returns the significant bits in bits, and the position
|
* Given a float value, returns the significant bits, and the position of the
|
||||||
* of the decimal point in $(D_PARAM exponent). +/-Inf and NaN are specified
|
* decimal point in $(D_PARAM exponent). +/-Inf and NaN are specified by
|
||||||
* by special values returned in the $(D_PARAM exponent). Sing bit is set in
|
* special values returned in the $(D_PARAM exponent). Sing bit is set in
|
||||||
* $(D_PARAM sign).
|
* $(D_PARAM sign).
|
||||||
*/
|
*/
|
||||||
private const(char)[] real2String(double value,
|
private const(char)[] real2String(double value,
|
||||||
@ -505,35 +505,9 @@ if (T.sizeof == U.sizeof)
|
|||||||
copy((&src)[0 .. 1], (&dest)[0 .. 1]);
|
copy((&src)[0 .. 1], (&dest)[0 .. 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
private void formatReal(T)(ref T arg, ref String result)
|
||||||
|
if (isFloatingPoint!T)
|
||||||
{
|
{
|
||||||
String result;
|
|
||||||
|
|
||||||
static if (is(Unqual!(Args[0]) == typeof(null)))
|
|
||||||
{
|
|
||||||
result.insertBack("null");
|
|
||||||
}
|
|
||||||
else static if(is(Unqual!(Args[0]) == bool))
|
|
||||||
{
|
|
||||||
result.insertBack(args[0] ? "true" : "false");
|
|
||||||
}
|
|
||||||
else static if (isSomeString!(Args[0])) // String
|
|
||||||
{
|
|
||||||
if (args[0] is null)
|
|
||||||
{
|
|
||||||
result.insertBack("null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.insertBack(args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else static if (isSomeChar!(Args[0])) // Char
|
|
||||||
{
|
|
||||||
result.insertBack(args[0]);
|
|
||||||
}
|
|
||||||
else static if (isFloatingPoint!(Args[0])) // Float
|
|
||||||
{
|
|
||||||
char[512] buffer; // Big enough for e+308 or e-307.
|
char[512] buffer; // Big enough for e+308 or e-307.
|
||||||
char[8] tail = 0;
|
char[8] tail = 0;
|
||||||
char[] bufferSlice = buffer[64 .. $];
|
char[] bufferSlice = buffer[64 .. $];
|
||||||
@ -542,7 +516,7 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
|||||||
int decimalPoint;
|
int decimalPoint;
|
||||||
|
|
||||||
// Read the double into a string.
|
// Read the double into a string.
|
||||||
auto realString = real2String(args[0], buffer, decimalPoint, negative);
|
auto realString = real2String(arg, buffer, decimalPoint, negative);
|
||||||
auto length = cast(uint) realString.length;
|
auto length = cast(uint) realString.length;
|
||||||
|
|
||||||
// Clamp the precision and delete extra zeros after clamp.
|
// Clamp the precision and delete extra zeros after clamp.
|
||||||
@ -551,9 +525,7 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
|||||||
{
|
{
|
||||||
length = precision;
|
length = precision;
|
||||||
}
|
}
|
||||||
while ((length > 1)
|
while ((length > 1) && (precision != 0) && (realString[length - 1] == '0'))
|
||||||
&& (precision != 0)
|
|
||||||
&& (realString[length - 1] == '0'))
|
|
||||||
{
|
{
|
||||||
--precision;
|
--precision;
|
||||||
--length;
|
--length;
|
||||||
@ -566,7 +538,7 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
|||||||
if (decimalPoint == special)
|
if (decimalPoint == special)
|
||||||
{
|
{
|
||||||
result.insertBack(realString);
|
result.insertBack(realString);
|
||||||
goto ParamEnd;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should we use sceintific notation?
|
// Should we use sceintific notation?
|
||||||
@ -727,8 +699,83 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
|||||||
|
|
||||||
result.insertBack(buffer[64 .. length]); // Number.
|
result.insertBack(buffer[64 .. length]); // Number.
|
||||||
result.insertBack(tail[1 .. tail[0] + 1]); // Tail.
|
result.insertBack(tail[1 .. tail[0] + 1]); // Tail.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatStruct(T)(ref T arg, ref String result)
|
||||||
|
if (is(T == struct))
|
||||||
|
{
|
||||||
|
template pred(alias f)
|
||||||
|
{
|
||||||
|
static if (f == "this")
|
||||||
|
{
|
||||||
|
// Exclude context pointer from nested structs.
|
||||||
|
enum bool pred = false;
|
||||||
}
|
}
|
||||||
else static if (isPointer!(Args[0])) // Pointer
|
else
|
||||||
|
{
|
||||||
|
enum bool pred = !isSomeFunction!(__traits(getMember, arg, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alias fields = Filter!(pred, __traits(allMembers, T));
|
||||||
|
|
||||||
|
static if (fields.length > 0)
|
||||||
|
{
|
||||||
|
printToString!"{}"(result, __traits(getMember, arg, fields[0]));
|
||||||
|
foreach (field; fields[1 .. $])
|
||||||
|
{
|
||||||
|
result.insertBack(", ");
|
||||||
|
printToString!"{}"(result, __traits(getMember, arg, field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref String printToString(string fmt, Args...)(return ref String result,
|
||||||
|
auto ref Args args)
|
||||||
|
{
|
||||||
|
alias Arg = Args[0];
|
||||||
|
|
||||||
|
static if (is(Unqual!Arg == typeof(null))) // null
|
||||||
|
{
|
||||||
|
result.insertBack("null");
|
||||||
|
}
|
||||||
|
else static if(is(Unqual!Arg == bool)) // Boolean
|
||||||
|
{
|
||||||
|
result.insertBack(args[0] ? "true" : "false");
|
||||||
|
}
|
||||||
|
else static if (isSomeChar!Arg || isSomeString!Arg) // String or char
|
||||||
|
{
|
||||||
|
result.insertBack(args[0]);
|
||||||
|
}
|
||||||
|
else static if (isInputRange!Arg
|
||||||
|
&& !isInfinite!Arg
|
||||||
|
&& isSomeChar!(ElementType!Arg)) // Stringish range
|
||||||
|
{
|
||||||
|
result.insertBack(args[0]);
|
||||||
|
}
|
||||||
|
else static if (is(Unqual!(typeof(args[0].stringify())) == String))
|
||||||
|
{
|
||||||
|
result.insertBack(args[0].stringify()[]);
|
||||||
|
}
|
||||||
|
else static if (is(Arg == class))
|
||||||
|
{
|
||||||
|
result.insertBack(args[0] is null ? "null" : args[0].toString());
|
||||||
|
}
|
||||||
|
else static if (is(Arg == interface))
|
||||||
|
{
|
||||||
|
result.insertBack(Arg.classinfo.name);
|
||||||
|
}
|
||||||
|
else static if (is(Arg == struct))
|
||||||
|
{
|
||||||
|
result.insertBack(Arg.stringof);
|
||||||
|
result.insertBack('(');
|
||||||
|
formatStruct(args[0], result);
|
||||||
|
result.insertBack(')');
|
||||||
|
}
|
||||||
|
else static if (isFloatingPoint!Arg) // Float
|
||||||
|
{
|
||||||
|
formatReal(args[0], result);
|
||||||
|
}
|
||||||
|
else static if (isPointer!Arg) // Pointer
|
||||||
{
|
{
|
||||||
char[size_t.sizeof * 2] buffer;
|
char[size_t.sizeof * 2] buffer;
|
||||||
size_t position = buffer.length;
|
size_t position = buffer.length;
|
||||||
@ -744,20 +791,26 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
|||||||
result.insertBack("0x");
|
result.insertBack("0x");
|
||||||
result.insertBack(buffer[position .. $]);
|
result.insertBack(buffer[position .. $]);
|
||||||
}
|
}
|
||||||
else static if (isIntegral!(Args[0])) // Integer
|
else static if (isIntegral!Arg) // Integer
|
||||||
{
|
{
|
||||||
char[21] buffer;
|
char[21] buffer;
|
||||||
result.insertBack(integral2String(args[0], buffer));
|
result.insertBack(integral2String(args[0], buffer));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static assert(false);
|
static assert(false,
|
||||||
|
"Formatting type " ~ Arg.stringof ~ " is not supported");
|
||||||
}
|
}
|
||||||
ParamEnd:
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package(tanya) String format(string fmt, Args...)(auto ref Args args)
|
||||||
|
{
|
||||||
|
String formatted;
|
||||||
|
return printToString!fmt(formatted, args);
|
||||||
|
}
|
||||||
|
|
||||||
// One argument tests.
|
// One argument tests.
|
||||||
@nogc pure @safe unittest
|
@nogc pure @safe unittest
|
||||||
{
|
{
|
||||||
@ -772,7 +825,7 @@ ParamEnd:
|
|||||||
|
|
||||||
// String printing.
|
// String printing.
|
||||||
assert(format!"{}"("Some weired string") == "Some weired string");
|
assert(format!"{}"("Some weired string") == "Some weired string");
|
||||||
assert(format!"{}"(cast(string) null) == "null");
|
assert(format!"{}"(cast(string) null) == "");
|
||||||
assert(format!"{}"('c') == "c");
|
assert(format!"{}"('c') == "c");
|
||||||
|
|
||||||
// Integer.
|
// Integer.
|
||||||
@ -826,6 +879,93 @@ ParamEnd:
|
|||||||
assert(format!"{}"(cast(void*) null) == "0x0");
|
assert(format!"{}"(cast(void*) null) == "0x0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Structs.
|
||||||
|
@nogc pure @safe unittest
|
||||||
|
{
|
||||||
|
static struct WithoutStringify1
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
void func()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(format!"{}"(WithoutStringify1(6)) == "WithoutStringify1(6)");
|
||||||
|
|
||||||
|
static struct WithoutStringify2
|
||||||
|
{
|
||||||
|
}
|
||||||
|
assert(format!"{}"(WithoutStringify2()) == "WithoutStringify2()");
|
||||||
|
|
||||||
|
static struct WithoutStringify3
|
||||||
|
{
|
||||||
|
int a = -2;
|
||||||
|
int b = 8;
|
||||||
|
}
|
||||||
|
assert(format!"{}"(WithoutStringify3()) == "WithoutStringify3(-2, 8)");
|
||||||
|
|
||||||
|
struct Nested
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
void func()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(format!"{}"(Nested()) == "Nested(0)");
|
||||||
|
|
||||||
|
static struct WithStringify
|
||||||
|
{
|
||||||
|
String stringify() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return String("stringify method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(format!"{}"(WithStringify()) == "stringify method");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate types.
|
||||||
|
@system unittest // Object.toString has no attributes.
|
||||||
|
{
|
||||||
|
import tanya.memory;
|
||||||
|
import tanya.memory.smartref;
|
||||||
|
|
||||||
|
interface I
|
||||||
|
{
|
||||||
|
}
|
||||||
|
class A : I
|
||||||
|
{
|
||||||
|
}
|
||||||
|
auto instance = defaultAllocator.unique!A();
|
||||||
|
assert(format!"{}"(instance.get()) == instance.get.toString());
|
||||||
|
assert(format!"{}"(cast(I) instance.get()) == I.classinfo.name);
|
||||||
|
assert(format!"{}"(cast(A) null) == "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranges.
|
||||||
|
@nogc pure @safe unittest
|
||||||
|
{
|
||||||
|
static struct Stringish
|
||||||
|
{
|
||||||
|
string content = "Some content";
|
||||||
|
|
||||||
|
immutable(char) front() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.content[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void popFront() @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
this.content = this.content[1 .. $];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.content.length == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(format!"{}"(Stringish()) == "Some content");
|
||||||
|
}
|
||||||
|
|
||||||
private struct FormatSpec
|
private struct FormatSpec
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -393,7 +393,9 @@ struct Integer
|
|||||||
T opCast(T)() const
|
T opCast(T)() const
|
||||||
if (isIntegral!T && isSigned!T)
|
if (isIntegral!T && isSigned!T)
|
||||||
{
|
{
|
||||||
return this.sign ? -(cast(Unsigned!T) this) : cast(Unsigned!T) this;
|
return this.sign
|
||||||
|
? cast(T) -(cast(Promoted!(Unsigned!T)) (cast(Unsigned!T) this))
|
||||||
|
: cast(Unsigned!T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1072,7 +1074,7 @@ struct Integer
|
|||||||
assert(h1 == 79);
|
assert(h1 == 79);
|
||||||
|
|
||||||
h2 = ~h1;
|
h2 = ~h1;
|
||||||
assert(h2 == ~cast(ubyte) 79);
|
assert(h2 == cast(ubyte) ~79);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ditto
|
/// ditto
|
||||||
|
@ -275,13 +275,37 @@ enum bool isTemplate(alias T) = __traits(isTemplate, T);
|
|||||||
static assert(!isTemplate!(S!int));
|
static assert(!isTemplate!(S!int));
|
||||||
}
|
}
|
||||||
|
|
||||||
deprecated("Use is(T == interface) instead")
|
/**
|
||||||
|
* Tests whether $(D_PARAM T) is an interface.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = A type.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is an interface,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
enum bool isInterface(T) = is(T == interface);
|
enum bool isInterface(T) = is(T == interface);
|
||||||
|
|
||||||
deprecated("Use is(T == class) instead")
|
/**
|
||||||
|
* Tests whether $(D_PARAM T) is a class.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = A type.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a class,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
enum bool isClass(T) = is(T == class);
|
enum bool isClass(T) = is(T == class);
|
||||||
|
|
||||||
deprecated("Use is(T == struct) instead")
|
/**
|
||||||
|
* Tests whether $(D_PARAM T) is a struct.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = A type.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a struct,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
enum bool isStruct(T) = is(T == struct);
|
enum bool isStruct(T) = is(T == struct);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user