Skip to content

Commit

Permalink
Revision
Browse files Browse the repository at this point in the history
Different approach:

* New file ImathTypeTraits.h now collects all the type traits (including
  a couple things we had previously put in ImathPlatform.h).

* New traits: has_subscript, has_xy, has_xyz, has_xyzw.

* New test file: testInterop.{h,cpp}

* Plain C arrays[3] work now.

* Classes that have .x, .y, .z member variables (but no subscripting)
  work now.

* Classes that have BOTH subscripting and named members work!

* It's easy to correct false positives and false negatives.

I still have only prototyped for Vec3, if people like the design, I will
extend to the other relevant types.

Signed-off-by: Larry Gritz <[email protected]>
  • Loading branch information
lgritz committed Feb 13, 2021
1 parent 94f3eb9 commit 29d33dc
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 123 deletions.
1 change: 1 addition & 0 deletions src/Imath/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ imath_define_library(Imath
ImathRoots.h
ImathShear.h
ImathSphere.h
ImathTypeTraits.h
ImathVecAlgo.h
ImathVec.h
half.h
Expand Down
20 changes: 0 additions & 20 deletions src/Imath/ImathPlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,6 @@ IMATH_INTERNAL_NAMESPACE_HEADER_ENTER
#endif


//
// Define Imath::enable_if_t to be std for C++14, equivalent for C++11.
//
#if (IMATH_CPLUSPLUS_VERSION >= 14)
using std::enable_if_t; // Use C++14 std::enable_if_t
#else
// Define enable_if_t for C++11
template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
#endif


//
// An enable_if helper to be used in template parameters which results in
// much shorter symbols.
//
#define IMATH_ENABLE_IF(...) Imath::enable_if_t<(__VA_ARGS__), int> = 0



#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
Expand Down
186 changes: 186 additions & 0 deletions src/Imath/ImathTypeTraits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//
// SPDX-License-Identifier: BSD-3-Clause
// Copyright Contributors to the OpenEXR Project.
//

//
// This file contains type traits related to or used by the Imath library.
//

#ifndef INCLUDED_IMATHTYPETRAITS_H
#define INCLUDED_IMATHTYPETRAITS_H

#include <type_traits>

#include "ImathPlatform.h"

IMATH_INTERNAL_NAMESPACE_HEADER_ENTER


/// Define Imath::enable_if_t to be std for C++14, equivalent for C++11.
#if (IMATH_CPLUSPLUS_VERSION >= 14)
using std::enable_if_t; // Use C++14 std::enable_if_t
#else
// Define enable_if_t for C++11
template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
#endif


/// An enable_if helper to be used in template parameters which results in
/// much shorter symbols.
#define IMATH_ENABLE_IF(...) Imath::enable_if_t<(__VA_ARGS__), int> = 0



/// @{
/// @name Detecting interoperable types.
///
/// In order to construct or assign from external "compatible" types without
/// prior knowledge of their definitions, we have a few helper type traits.
/// The intent of these is to allow custom linear algebra types in an
/// application that have seamless conversion to and from Imath types.
///
/// `has_xy<T,Base>`, `has_xyz<T,Base>`, `has_xyzw<T,Base>` detect if class
/// `T` has elements `.x`, `.y`, and `.z` all of type `Base` and seems to be
/// the right size to hold exactly those members and nothing more.
///
/// `has_subscript<T,Base,N>` detects if class `T` can perform `T[int]`
/// to yield a `Base`, and that it seems to be exactly the right size to
/// hold `N` of those elements.
///
/// This is not exact. It's possible that for a particular user-defined
/// type, this may yield a false negative or false positive. For example:
/// * A class for a 3-vector that contains an extra element of padding
/// so that it will have the right size and alignment to use 4-wide
/// SIMD math ops will appear to be the wrong size.
/// * A `std::vector<T>` is subscriptable and might have N elements at
/// runtime, but the size is dynamic and so would fail this test.
/// * A foreign type may have .x, .y, .z that are not matching our base
/// type but we still want it to work (with appropriate conversions).
///
/// In these cases, user code may declare an exception -- for example,
/// stating that `mytype` should be considered implicitly convertible to
/// an Imath::V3f by subscripting:
///
/// template<>
/// struct Imath::has_subscript<mytype, float, 3> : public std::true_type { };
///
/// And similarly, user code may correct a potential false positive (that
/// is, a `mytype` looks like it should be convertible to a V3f, but you
/// don't want it to ever happen):
///
/// template<typename B, int N>
/// struct Imath::has_subscript<mytype, B, N> : public std::false_type { };
///


/// `has_xy<T,Base>::value` will be true if type `T` has member variables
/// `.x` and `.y`, all of type `Base`, and the size of a `T` is exactly big
/// enough to hold 2 Base values.
template <typename T, typename Base>
struct has_xy {
private:
typedef char Yes[1];
typedef char No[2];

// Valid only if .x, .y, .z exist and are the right type: return a Yes.
template<typename C,
IMATH_ENABLE_IF(std::is_same<decltype(C().x), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().y), Base>::value)>
static Yes& test(int);

// Fallback, default to returning a No.
template<typename C> static No& test(...);
public:
enum { value = (sizeof(test<T>(0)) == sizeof(Yes)
&& sizeof(T) == 2*sizeof(Base))
};
};


/// `has_xyz<T,Base>::value` will be true if type `T` has member variables
/// `.x`, `.y`, and `.z`, all of type `Base`, and the size of a `T` is
/// exactly big enough to hold 3 Base values.
template <typename T, typename Base>
struct has_xyz {
private:
typedef char Yes[1];
typedef char No[2];

// Valid only if .x, .y, .z exist and are the right type: return a Yes.
template<typename C,
IMATH_ENABLE_IF(std::is_same<decltype(C().x), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().y), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().z), Base>::value)>
static Yes& test(int);

// Fallback, default to returning a No.
template<typename C> static No& test(...);
public:
enum { value = (sizeof(test<T>(0)) == sizeof(Yes)
&& sizeof(T) == 3*sizeof(Base))
};
};


/// `has_xyzw<T,Base>::value` will be true if type `T` has member variables
/// `.x`, `.y`, `.z`, and `.w`, all of type `Base`, and the size of a `T` is
/// exactly big enough to hold 4 Base values.
template <typename T, typename Base>
struct has_xyzw {
private:
typedef char Yes[1];
typedef char No[2];

// Valid only if .x, .y, .z exist and are the right type: return a Yes.
template<typename C,
IMATH_ENABLE_IF(std::is_same<decltype(C().x), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().y), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().z), Base>::value),
IMATH_ENABLE_IF(std::is_same<decltype(C().w), Base>::value)>
static Yes& test(int);

// Fallback, default to returning a No.
template<typename C> static No& test(...);
public:
enum { value = (sizeof(test<T>(0)) == sizeof(Yes)
&& sizeof(T) == 4*sizeof(Base))
};
};



/// `has_subscript<T,Base,N>::value` will be true if type `T` has
/// subscripting syntax, a `T[int]` returns a `Base`, and the size of a `T`
/// is exactly big enough to hold `N` `Base` values.
template <typename T, typename Base, int N>
struct has_subscript {
private:
typedef char Yes[1];
typedef char No[2];

// Valid only if T[] is possible and is the right type: return a Yes.
template<typename C,
IMATH_ENABLE_IF(std::is_same<typename std::decay<decltype(C()[0])>::type, Base>::value)>
static Yes& test(int);

// Fallback, default to returning a No.
template<typename C> static No& test(...);
public:
enum { value = (sizeof(test<T>(0)) == sizeof(Yes)
&& sizeof(T) == N*sizeof(Base))
};
};


/// C arrays of just the right length also are qualified for has_subscript.
template<typename Base, int N>
struct has_subscript<Base[N], Base, N> : public std::true_type { };

/// @}


IMATH_INTERNAL_NAMESPACE_HEADER_EXIT

#endif // INCLUDED_IMATHTYPETRAITS_H
30 changes: 17 additions & 13 deletions src/Imath/ImathVec.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "ImathMath.h"
#include "ImathNamespace.h"
#include "ImathTypeTraits.h"

#include <iostream>
#include <limits>
Expand All @@ -25,17 +26,6 @@

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 @@ -304,7 +294,12 @@ template <class T> class Vec3

/// 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))>
template<typename V, IMATH_ENABLE_IF(has_xyz<V,T>::value)>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Vec3 (const V& v) noexcept
: Vec3(v.x, v.y, v.z) { }

template<typename V, IMATH_ENABLE_IF(has_subscript<V,T,3>::value
&& !has_xyz<V,T>::value)>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Vec3 (const V& v) noexcept
: Vec3(v[0], v[1], v[2]) { }

Expand All @@ -323,7 +318,16 @@ template <class T> class Vec3

/// 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))>
template<typename V, IMATH_ENABLE_IF(has_xyz<V,T>::value)>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 const Vec3& operator= (const V& v) noexcept {
x = v.x;
y = v.y;
z = v.z;
return *this;
}

template<typename V, IMATH_ENABLE_IF(has_subscript<V,T,3>::value
&& !has_xyz<V,T>::value)>
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 const Vec3& operator= (const V& v) noexcept {
x = v[0];
y = v[1];
Expand Down
1 change: 1 addition & 0 deletions src/ImathTest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_executable(ImathTest
testLimits.cpp
testSize.cpp
testToFloat.cpp
testInterop.cpp
)

target_link_libraries(ImathTest Imath::Imath)
Expand Down
2 changes: 2 additions & 0 deletions src/ImathTest/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <testFrustum.h>
#include <testFrustumTest.h>
#include <testFun.h>
#include <testInterop.h>
#include <testInterval.h>
#include <testInvert.h>
#include <testJacobiEigenSolver.h>
Expand Down Expand Up @@ -85,6 +86,7 @@ main (int argc, char* argv[])
TEST (testTinySVD);
TEST (testJacobiEigenSolver);
TEST (testFrustumTest);
TEST (testInterop);
// NB: If you add a test here, make sure to enumerate it in the
// CMakeLists.txt so it runs as part of the test suite

Expand Down
Loading

0 comments on commit 29d33dc

Please sign in to comment.