Jump to page: 1 2
Thread overview
C++ vs D: Default param values and struct to array casting
Sep 06
a11e99z
September 06
C++ allows the for following:

struct Demo
{
	float a, b, c, d;
	Demo() { a = b = c = d = 0.0f; }
	Demo(float _a, float _b, float _c, float _d) {
		a = _a;
		b = _b;
		c = _c;
		d = _d;
	}
	float  operator[] (size_t i) const { return (&a)[i]; } //[3]
	float& operator[] (size_t i) { return (&a)[i]; } //[4]
}

void fun(float col[3])
{
	// do fun stuff
}

void moreFun(const Demo& d = Demo(0.1f, 0.3f, 7.5f, 1.5f)) // [1]
{
	// you guessed it... more fun stuff
}

int main(int argv, const char** argc)
{
	Demo inst = Demo(0.1f, 0.3f, 7.5f, 1.5f);
	fun((float*)&inst); // [2]
	moreFun();
	return 0;
}

I'm seeking some pointers on how to define these in D so that I can instantiate them in D while still linking to the associated C++ library or object file for the implementation. The main points of contention are at [1] and [2] because I have no idea how to accomplish these.  I assume that I can accomplish [3] and [4] with opIndex() and opIndexAssign(), however I'm not understanding the slicing of the memory address at the first member variable. I know that by indexing the memory address at the member variable we are able treat the struct as an array but i do not understand how to implement it in D.

I read the documentation at https://dlang.org/spec/cpp_interface.html and https://dlang.org/articles/cpptod.html but it was not clear "to me" how to overcome these issues.

Andrew
September 06
On Friday, 6 September 2019 at 09:14:31 UTC, Andrew Edwards wrote:
> C++ allows the for following:
>
> struct Demo
> {
> 	float a, b, c, d;
> 	Demo() { a = b = c = d = 0.0f; }
> 	Demo(float _a, float _b, float _c, float _d) {
> 		a = _a;
> 		b = _b;
> 		c = _c;
> 		d = _d;
> 	}
> 	float  operator[] (size_t i) const { return (&a)[i]; } //[3]
> 	float& operator[] (size_t i) { return (&a)[i]; } //[4]
> }

This is my thought on how to accomplish op overloading:

struct Test
{
    float a, b, c, d;
    float opIndex(size_t i)
    in(i >= 0 && i <= 3)
    {
        final switch(i)
        {
            case 0: return a;
            case 1: return b;
            case 2: return c;
            case 3: return b;
        }
    }

    void opIndexAssign(float val, size_t i)
    in(i >= 0 && i <= 3)
    {
        final switch(i)
        {
            case 0: a = val; break;
            case 1: b = val; break;
            case 2: c = val; break;
            case 3: d = val; break;
        }
    }
}

Please advise if I've gone off the beaten path. It seems overkill for the two lines of code C++ requires so any suggestion is greatly appreciated.
September 06
On Friday, 6 September 2019 at 09:14:31 UTC, Andrew Edwards wrote:
> C++ allows the for following:
>
> struct Demo
> {
> 	float a, b, c, d;
> 	Demo() { a = b = c = d = 0.0f; }
> 	Demo(float _a, float _b, float _c, float _d) {
> 		a = _a;
> 		b = _b;
> 		c = _c;
> 		d = _d;
> 	}
> 	float  operator[] (size_t i) const { return (&a)[i]; } //[3]

"(&a)[i]" is undefined behavior in C++. You cannot index into struct members like that. Of course it may work in certain cases, but UB is UB. Don't do it!

I found a more detailed explanation for you: https://stackoverflow.com/questions/40590216/is-it-legal-to-index-into-a-struct

-Johan

September 06
On Friday, 6 September 2019 at 09:49:33 UTC, Johan Engelen wrote:
> On Friday, 6 September 2019 at 09:14:31 UTC, Andrew Edwards wrote:
>> C++ allows the for following:
>>
>> struct Demo
>> {
>> 	float a, b, c, d;
>> 	Demo() { a = b = c = d = 0.0f; }
>> 	Demo(float _a, float _b, float _c, float _d) {
>> 		a = _a;
>> 		b = _b;
>> 		c = _c;
>> 		d = _d;
>> 	}
>> 	float  operator[] (size_t i) const { return (&a)[i]; } //[3]
>
> "(&a)[i]" is undefined behavior in C++. You cannot index into struct members like that. Of course it may work in certain cases, but UB is UB. Don't do it!
>
> I found a more detailed explanation for you: https://stackoverflow.com/questions/40590216/is-it-legal-to-index-into-a-struct
>
> -Johan

Thanks for the clarification and link. This is a example taken from a popular library I’m trying to port to D. I’m not trying to do it in C++ myself, just to understand how to interface to the code so that I can get a reference example compiled in D.

Andrew
September 06
On Friday, 6 September 2019 at 09:14:31 UTC, Andrew Edwards wrote:
> C++ allows the for following:
>
> struct Demo
> {
> 	float a, b, c, d;
> 	Demo() { a = b = c = d = 0.0f; }
> 	Demo(float _a, float _b, float _c, float _d) {
> 		a = _a;
> 		b = _b;
> 		c = _c;
> 		d = _d;
> 	}
> 	float  operator[] (size_t i) const { return (&a)[i]; } //[3]
> 	float& operator[] (size_t i) { return (&a)[i]; } //[4]
> }
>

https://dlang.org/spec/simd.html

also probably u can do https://run.dlang.io/is/WMQE93

September 06
On Friday, 6 September 2019 at 09:28:57 UTC, Andrew Edwards wrote:
> This is my thought on how to accomplish op overloading:
>
> struct Test
> {
>     float a, b, c, d;
>     float opIndex(size_t i)
>     in(i >= 0 && i <= 3)
>     {
>         final switch(i)
>         {
>             case 0: return a;
>             case 1: return b;
>             case 2: return c;
>             case 3: return b;
>         }
>     }
>
>     void opIndexAssign(float val, size_t i)
>     in(i >= 0 && i <= 3)
>     {
>         final switch(i)
>         {
>             case 0: a = val; break;
>             case 1: b = val; break;
>             case 2: c = val; break;
>             case 3: d = val; break;
>         }
>     }
> }
>
> Please advise if I've gone off the beaten path. It seems overkill for the two lines of code C++ requires so any suggestion is greatly appreciated.

You can use `static foreach` to eliminate the repetition in the switch statements. For example:

float opIndex(size_t i)
    in (i < typeof(this).tupleof.length)
{
    final switch (i) {
        static foreach (fid, field; typeof(this).tupleof) {
            case fid: return field;
        }
    }
}

September 06
On 09/06/2019 02:14 AM, Andrew Edwards wrote:

> I'm seeking some pointers on how to define these in D

Here is my attempt:

struct Demo {
  // NOTE: The types and number of elements can be templatized and mixed-in like
  // mixin (makeMembers!T(N));

  float a = 0;
  float b = 0;
  float c = 0;
  float d = 0;

  // Then these checks would be unnecessary
  static assert(a.sizeof == b.sizeof);
  static assert(b.sizeof == c.sizeof);
  static assert(c.sizeof == d.sizeof);

  // And this would be equal to N
  enum length = this.sizeof / a.sizeof;

  this(const(float)[] init...) {
    asSlice[0..init.length] = init;
  }

  auto asSlice() inout {
    // WARNING: As UNDEFINED BEHAVIOR as in the C++ code!
    return (&a)[0..length];
  }

  ref asArray(size_t n = length)() inout {
    // WARNING: As UNDEFINED BEHAVIOR as in the C++ code!
    return *cast(float[n]*)(&a);
  }

  ref inout(float) opIndex(size_t i) inout {  // [3] [4]
    return asSlice[i];
  }
}

unittest {
  import std.algorithm;

  static assert (Demo.length == 4);

  void enforceMemberWiseEquality(Demo d, const(float)[] values) {
    switch (values.length) {
    case 4:
      assert(d.d == values[3]);
      goto case;

    case 3:
      assert(d.c == values[2]);
      goto case;

    case 2:
      assert(d.b == values[1]);
      goto case;

    case 1:
      assert(d.a == values[0]);
      goto default;

    default:
      break;
    }
  }

  const init = [1.0f, 2.0f, 3.0f, 4.0f];

  // Test constructing with different number of expressions
  foreach (length; 0..init.length) {
    auto testInit = init[0..length];
    const d = Demo(testInit);
    enforceMemberWiseEquality(d, testInit);
    assert(d.asArray[0..length].equal(testInit));
    assert(d.asSlice[0..length].equal(testInit));}
}

extern (C++) void fun(float* col) {
  // do fun stuff
}

extern (C++) void moreFun(ref const(Demo) d) { // [1]
  // you guessed it... more fun stuff
}

// This is needed because operations inside asSlice are not allowed
// at compile time. (That's why I could not make this variable 'static'.
immutable(Demo) moreFun_default;
shared static this() {
  moreFun_default = Demo(0.1f, 0.3f, 7.5f, 1.5f);
}

extern (C++) void moreFun() { // [1]
  // We need this function because D does not allow rvalue references.
  // Here, we're passing an lvalue.
  moreFun(moreFun_default);
}

int main()
{
  Demo inst = Demo(0.1f, 0.3f, 7.5f, 1.5f);
  fun(inst.asSlice.ptr); // [2]
  moreFun();
  return 0;
}

Ali

September 06
On Friday, 6 September 2019 at 11:35:59 UTC, a11e99z wrote:
>
> https://dlang.org/spec/simd.html

This didn't work two well because I wont be able to access the members by name as C++ library expects. Will consider during refactoring.

> also probably u can do https://run.dlang.io/is/WMQE93

Ended up using this since it provides for named access and solves the overloading requirement.

Thanks,
Andrew
September 06
On Friday, 6 September 2019 at 18:39:47 UTC, Andrew Edwards wrote:
>> also probably u can do https://run.dlang.io/is/WMQE93
>
> Ended up using this since it provides for named access and solves the overloading requirement.
>
> Thanks,
> Andrew

You can also use std.typecons.Tuple for this, since it provides both named and indexed access:

https://run.dlang.io/is/bB21sX
September 07
On Friday, 6 September 2019 at 18:31:29 UTC, Ali Çehreli wrote:
> On 09/06/2019 02:14 AM, Andrew Edwards wrote:
>
> > I'm seeking some pointers on how to define these in D
>
> Here is my attempt:
>

Ali, this is awesome. It solves all 4 problems in on shot. I definitely don't intend on using the undefined aspect in the future but it is welcomed at the moment.

Much appreciated.
Andrew


« First   ‹ Prev
1 2