Skip to content

Commit

Permalink
RFC: Interoperability constructors
Browse files Browse the repository at this point in the history
This is a design review, feedback requested before I proceed further.

Trying out some use of enable_if to give `Vec3<T>` constructor and
assignment from unknown types that "look like" 3-vectors -- they have
a `operator[]` that returns a T, and their size is at least
3*sizeof(T).

The main idea is that if an app has rolled its own 3-vector class, it
can seamlessly construct a Vec3 from it, assign a Vec3 to it, or pass
their type as a parameter that expects a Vec3. And if the app's custom
3-vector class employs an equivalent idiom, all those operations will
work in the reverse direction.

It also works for std::vector, std::array, and even "initializer lists",
so all of this would work:

    // Your app's special snowflake custom 3-vector type.
    class YourCustomVec3 { ... };

    // My library has an API call where I use Imath::Vec3 as a parameter
    // passing convention, because I don't know about your vector.
    void myfunc (const Vec3<float>& v) { ... }

    // All sorts of things people may think of as "a vector"
    YourCustomVec3 myvec(1, 2, 3);
    std::array<float,3> arr { 1, 2, 3 };
    std::vector<float> stdvec { 1, 2, 3 };

    myfunc(yourvec);            // ok
    myfunc(arr);                // yep
    myfunc(stdvec);             // hunky-dory
    myfunc({ 1.0, 2.0, 3.0 });  // yeah, even that

This is only prototyped for Vec3 for now, but if you all like this
idea, then I will complete the work to do this for the other vector
and matrix classes.

Signed-off-by: Larry Gritz <[email protected]>
  • Loading branch information
lgritz committed Feb 8, 2021
1 parent 1e7bb58 commit 94f3eb9
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/Imath/ImathVec.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@

IMATH_INTERNAL_NAMESPACE_HEADER_ENTER


/// Does type V appear to be equivalent to an Imath::VecN<Base>? We assume
/// that it is if it has a subscript operator that returns a Base, and its
/// size is large enough to contain N elements of type Base.
#define IMATH_VECTOR_EQUIVALENT(V,Base,N) \
(std::is_same<typename std::decay<decltype(V()[0])>::type, Base>::value \
&& sizeof(V) >= N*sizeof(Base))




template <class T> class Vec2;
template <class T> class Vec3;
template <class T> class Vec4;
Expand Down Expand Up @@ -291,6 +302,12 @@ template <class T> class Vec3
/// Construct from Vec3 of another base type
template <class S> IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Vec3 (const Vec3<S>& v) noexcept;

/// Interoperability constructor from another type that behaves as if it
/// were an equivalent vector.
template<typename V, IMATH_ENABLE_IF(IMATH_VECTOR_EQUIVALENT(V, T, 3))>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Vec3 (const V& v) noexcept
: Vec3(v[0], v[1], v[2]) { }

/// Vec4 to Vec3 conversion: divide x, y and z by w, even if w is
/// 0. The result depends on how the environment handles
/// floating-point exceptions.
Expand All @@ -304,6 +321,16 @@ template <class T> class Vec3
/// Assignment
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 const Vec3& operator= (const Vec3& v) noexcept;

/// Interoperability assignment from another type that behaves as if it
/// were an equivalent vector.
template<typename V, IMATH_ENABLE_IF(IMATH_VECTOR_EQUIVALENT(V, T, 3))>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 const Vec3& operator= (const V& v) noexcept {
x = v[0];
y = v[1];
z = v[2];
return *this;
}

/// Destructor
~Vec3() noexcept = default;

Expand Down
90 changes: 90 additions & 0 deletions src/ImathTest/testVec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

#include "ImathFun.h"
#include "ImathVec.h"
#include <array>
#include <cassert>
#include <cmath>
#include <iostream>
#include <vector>
#include <testVec.h>

using namespace std;
Expand Down Expand Up @@ -237,6 +239,92 @@ testLength4T()
assert (IMATH_INTERNAL_NAMESPACE::equal (v.normalized().length(), 1, e));
}



// Extra simple vector that does nothing but allow element access, as an
// example of a vector type that an app might use and want interop with
// our vectors.
template <typename T, int N>
struct SimpleVec {
T elements[N];

SimpleVec(T val = T(0)) {
for (int i = 0; i < N; ++i)
elements[i] = val;
}
~SimpleVec() = default;
constexpr T operator[](int i) const { return elements[i]; }
T& operator[](int i) { return elements[i]; }
};



void
testInteropPass3(const Vec3<float>& v, float a, float b, float c)
{
assert(v[0] == a && v[1] == b && v[2] == c);
}

void
testInteropCtr3()
{
// Test construction/assignment/paramater pass of a different vector type
{
SimpleVec<float,3> s;
s[0] = 1;
s[1] = 2;
s[2] = 3;
Vec3<float> v(s);
assert(v[0] == 1 && v[1] == 2 && v[2] == 3);
s[1] = 42;
v = s;
assert(v[0] == 1 && v[1] == 42 && v[2] == 3);

testInteropPass3(s, 1, 42, 3);
}
// Test construction/assignment/paramater pass of a std::vector of length 3
{
std::vector<float> s { 1, 2, 3 };
Vec3<float> v(s);
assert(v[0] == 1 && v[1] == 2 && v[2] == 3);
s[1] = 42;
v = s;
assert(v[0] == 1 && v[1] == 42 && v[2] == 3);
testInteropPass3(s, 1, 42, 3);
}
// Test construction/assignment/paramater pass of a std::array of length 3
{
std::array<float, 3> s { 1, 2, 3 };
Vec3<float> v(s);
assert(v[0] == 1 && v[1] == 2 && v[2] == 3);
s[1] = 42;
v = s;
assert(v[0] == 1 && v[1] == 42 && v[2] == 3);
testInteropPass3(s, 1, 42, 3);
}
// Test construction/assignment/paramater pass of initializer lists.
{
Vec3<float> v({ 1.0f, 2.0f, 3.0f });
assert(v[0] == 1 && v[1] == 2 && v[2] == 3);
v = { 1.0f, 42.0f, 3.0f };
assert(v[0] == 1 && v[1] == 42 && v[2] == 3);
testInteropPass3({ 1.0f, 42.0f, 3.0f }, 1, 42, 3);
}
#if 0
// Test construction/assignment/paramater pass of a C array
{
float s[3] = { 1, 2, 3 };
Vec3<float> v(s);
assert(v[0] == 1 && v[1] == 2 && v[2] == 3);
s[1] = 42;
v = s;
assert(v[0] == 1 && v[1] == 42 && v[2] == 3);
testInteropPass3(s, 1, 42, 3);
}
#endif
}


} // namespace

void
Expand All @@ -251,5 +339,7 @@ testVec()
testLength4T<float>();
testLength4T<double>();

testInteropCtr3();

cout << "ok\n" << endl;
}

0 comments on commit 94f3eb9

Please sign in to comment.