Skip to content

Commit

Permalink
Deprecate construction from std::array and from pointer.
Browse files Browse the repository at this point in the history
These features can be re-enabled by specializing the linalg::converter<T,U> type, as shown in tests/test-user-defined-conversions.cpp
  • Loading branch information
sgorsten committed Oct 15, 2018
1 parent 032a5c5 commit dd95a16
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 89 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ It is inspired by the syntax of popular shader languages and intended to serve a
* `lerp(a,b,t)` has been generalized to a component-wise operation where any of `a`, `b`, and `t` can be vectors or scalars
* `vec<T,M>` elements can be referred to via `x`,`y`,`z`,`w` or `r`,`g`,`b`,`a` or `s`,`t`,`p`,`q`
* User can specialize `converter<T,U>` to enable implicit conversions from `U` to `T`, if either type is a `vec`, `mat`, or `quat`
* `identity` is implemented using this facility to serve as an in-library example
* No undefined behavior according to the C++11 standard
* Almost all operations which do not internally call `<cmath>` functions are `constexpr`, except for `argmin` and `argmax`
* No lambdas are used in `linalg.h`, avoidng potential ODR violations
Expand All @@ -49,6 +50,10 @@ It is inspired by the syntax of popular shader languages and intended to serve a
* However, componentwise operations can still be invoked manually with the `apply(...)` function
* `vec<T,M>` and `mat<T,M,N>` are now defined in terms of arrays, instead of explicit fields `x`, `y`, `z`, `w`
* However, `vec<T,M>` can still be accessed with `x`,`y`,`z`,`w` due to special scalar accessor types which should silently convert to `T` in most scenarios
* Some constructors have been removed from `vec` and `mat`
* `vec<T,M>` no longer has an implicit constructor from `std::array<T,M>`
* `vec<T,M>` and `mat<T,M,N>` no longer have an explicit constructor from `const T *`
* These capabilities can be added by specializing `converter<T,U>`, as shown in [test-user-defined-conversions.cpp](tests/test-user-defined-conversions.cpp)
* `vec::xy()` and `vec::xyz()` can no longer be used as lvalues
* The original behavior relied on `reinterpret_cast` operations which were undefined behavior in standard C++
* `a.xyz() = {1,2,3}` can be rewritten as `a = {1,2,3,a.w};`
Expand All @@ -58,11 +63,11 @@ It is inspired by the syntax of popular shader languages and intended to serve a

This section has been removed pending a complete rewrite. Documentation needs to be provided for the following symbols.

- [ ] `identity`
- [ ] `struct vec<T,M>`: `elems`, accessors, constructors, `operator[]`, `xy`, `xyz`
- [ ] `struct mat<T,M,N>`: `cols`, constructors, `operator[]`, `row`
- [ ] `struct quat<T>`: `x`, `y`, `z`, `w`, constructors, `xyz`
- [ ] user-defined conversions: `converter<T,U>`
- [ ] `identity`
- [ ] higher-order functions: `fold`, `apply`, `map`, `zip`, `apply_t`
- [ ] three-way comparison: `compare`
- [ ] [`EqualityComparable`](http://en.cppreference.com/w/cpp/concept/EqualityComparable): `operator ==, !=`
Expand Down
35 changes: 10 additions & 25 deletions linalg.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
#include <cstdlib> // For std::abs
#include <cstddef> // For std::nullptr_t
#include <cstdint> // For std::uint8_t, std::uint16_t, std::int16_t, etc.
#include <array> // For std::array
#include <type_traits> // For std::is_arithmetic, std::is_same, std::enable_if, std::conditional
#include <functional> // For std::hash

// In Visual Studio 2015, `constexpr` applied to a member function implies `const`, which causes ambiguous overload resolution
#if _MSC_VER <= 1900
Expand All @@ -78,6 +79,14 @@ namespace linalg
// Specialize converter<T,U> with a function application operator that converts type U to type T to enable implicit conversions
template<class T, class U> struct converter {};

// Define a type which will convert to the multiplicative identity of any given algebraic object
struct identity_t { constexpr explicit identity_t(int) {} };
template<class T> struct converter<mat<T,2,2>, identity_t> { mat<T,2,2> operator() (identity_t) const { return {{1,0},{0,1}}; } };
template<class T> struct converter<mat<T,3,3>, identity_t> { mat<T,3,3> operator() (identity_t) const { return {{1,0,0},{0,1,0},{0,0,1}}; } };
template<class T> struct converter<mat<T,4,4>, identity_t> { mat<T,4,4> operator() (identity_t) const { return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; } };
template<class T> struct converter<quat<T>, identity_t> { quat<T> operator() (identity_t) const { return {0,0,0,1}; } };
constexpr identity_t identity {1};

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation details. Do not make use of the contents of this namespace from outside the library. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -102,9 +111,6 @@ namespace linalg
template<class T, class A, int I> struct unpack<scalar_accessor<T,A,I>> { using type=T; };
template<class T> using unpack_t = typename unpack<T>::type;

// Type with an implicit conversion to the multiplicative identity of any given algebraic object
struct identity_t { constexpr identity_t(int) {}; };

// Type returned by the compare(...) function which supports all six comparison operators against 0
template<class T> struct ord { T a,b; };
template<class T> constexpr bool operator == (const ord<T> & o, std::nullptr_t) { return o.a == o.b; }
Expand Down Expand Up @@ -238,10 +244,8 @@ namespace linalg
};
constexpr vec() : elems{} {}
constexpr vec(const vec & v) : elems{v[0], v[1]} {}
constexpr vec(const std::array<T,2> & a) : elems{a[0], a[1]} {}
constexpr vec(const T & e0, const T & e1) : elems{e0, e1} {}
constexpr explicit vec(const T & s) : elems{s, s} {}
constexpr explicit vec(const T * p) : elems{p[0], p[1]} {}
template<class U> constexpr explicit vec(const vec<U,2> & v) : elems{static_cast<T>(v[0]), static_cast<T>(v[1])} {}
constexpr const T & operator[] (int i) const { return elems[i]; }
LINALG_CONSTEXPR14 T & operator[] (int i) { return elems[i]; }
Expand All @@ -262,11 +266,9 @@ namespace linalg
};
constexpr vec() : elems{} {}
constexpr vec(const vec & v) : elems{v[0], v[1], v[2]} {}
constexpr vec(const std::array<T,3> & a) : elems{a[0], a[1], a[2]} {}
constexpr vec(const T & e0, const T & e1, const T & e2) : elems{e0, e1, e2} {}
constexpr vec(const vec<T,2> & e01, const T & e2) : elems{e01[0], e01[1], e2} {}
constexpr explicit vec(const T & s) : elems{s, s, s} {}
constexpr explicit vec(const T * p) : elems{p[0], p[1], p[2]} {}
template<class U> constexpr explicit vec(const vec<U,3> & v) : elems{static_cast<T>(v[0]), static_cast<T>(v[1]), static_cast<T>(v[2])} {}
constexpr const T & operator[] (int i) const { return elems[i]; }
LINALG_CONSTEXPR14 T & operator[] (int i) { return elems[i]; }
Expand All @@ -289,12 +291,10 @@ namespace linalg
};
constexpr vec() : elems{} {}
constexpr vec(const vec & v) : elems{v[0], v[1], v[2], v[3]} {}
constexpr vec(const std::array<T,4> & a) : elems{a[0], a[1], a[2], a[3]} {}
constexpr vec(const T & e0, const T & e1, const T & e2, const T & e3) : elems{e0, e1, e2, e3} {}
constexpr vec(const vec<T,2> & e01, const T & e2, const T & e3) : elems{e01[0], e01[1], e2, e3} {}
constexpr vec(const vec<T,3> & e012, const T & e3) : elems{e012[0], e012[1], e012[2], e3} {}
constexpr explicit vec(const T & s) : elems{s, s, s, s} {}
constexpr explicit vec(const T * p) : elems{p[0], p[1], p[2], p[3]} {}
template<class U> constexpr explicit vec(const vec<U,4> & v) : elems{static_cast<T>(v[0]), static_cast<T>(v[1]), static_cast<T>(v[2]), static_cast<T>(v[3])} {}
constexpr explicit vec(const quat<T> & q) : elems{q.x, q.y, q.z, q.w} {}
constexpr const T & operator[] (int i) const { return elems[i]; }
Expand All @@ -317,10 +317,8 @@ namespace linalg
using V=vec<T,M>;
V cols[2];
constexpr mat() : cols{} {}
constexpr mat(detail::identity_t) : cols{{1,0},{0,1}} {}
constexpr mat(const V & x_, const V & y_) : cols{x_, y_} {}
constexpr explicit mat(const T & s) : cols{V(s), V(s)} {}
constexpr explicit mat(const T * p) : cols{V(p+M*0), V(p+M*1)} {}
template<class U> constexpr explicit mat(const mat<U,M,2> & m) : cols{V(m[0]), V(m[1])} {}
constexpr const V & operator[] (int j) const { return cols[j]; }
LINALG_CONSTEXPR14 V & operator[] (int j) { return cols[j]; }
Expand All @@ -334,10 +332,8 @@ namespace linalg
using V=vec<T,M>;
V cols[3];
constexpr mat() : cols{} {}
constexpr mat(detail::identity_t) : cols{{1,0,0},{0,1,0},{0,0,1}} {}
constexpr mat(const V & x_, const V & y_, const V & z_) : cols{x_, y_, z_} {}
constexpr explicit mat(const T & s) : cols{V(s), V(s), V(s)} {}
constexpr explicit mat(const T * p) : cols{V(p+M*0), V(p+M*1), V(p+M*2)} {}
template<class U> constexpr explicit mat(const mat<U,M,3> & m) : cols{V(m[0]), V(m[1]), V(m[2])} {}
constexpr const V & operator[] (int j) const { return cols[j]; }
LINALG_CONSTEXPR14 V & operator[] (int j) { return cols[j]; }
Expand All @@ -351,10 +347,8 @@ namespace linalg
using V=vec<T,M>;
V cols[4];
constexpr mat() : cols{} {}
constexpr mat(detail::identity_t) : cols{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}} {}
constexpr mat(const V & x_, const V & y_, const V & z_, const V & w_) : cols{x_, y_, z_, w_} {}
constexpr explicit mat(const T & s) : cols{V(s), V(s), V(s), V(s)} {}
constexpr explicit mat(const T * p) : cols{V(p+M*0), V(p+M*1), V(p+M*2), V(p+M*3)} {}
template<class U> constexpr explicit mat(const mat<U,M,4> & m) : cols{V(m[0]), V(m[1]), V(m[2]), V(m[3])} {}
constexpr const V & operator[] (int j) const { return cols[j]; }
LINALG_CONSTEXPR14 V & operator[] (int j) { return cols[j]; }
Expand All @@ -372,25 +366,16 @@ namespace linalg
{
T x,y,z,w;
constexpr quat() : x(), y(), z(), w() {}
constexpr quat(detail::identity_t) : quat(0,0,0,1) {}
constexpr quat(const T & x_, const T & y_, const T & z_, const T & w_) : x(x_), y(y_), z(z_), w(w_) {}
constexpr quat(const vec<T,3> & xyz, const T & w_) : quat(xyz[0], xyz[1], xyz[2], w_) {}
constexpr explicit quat(const vec<T,4> & xyzw) : quat(xyzw[0], xyzw[1], xyzw[2], xyzw[3]) {}
constexpr explicit quat(const std::array<T,4> & a) : quat(a[0], a[1], a[2], a[3]) {}
template<class U> constexpr explicit quat(const quat<U> & q) : quat(static_cast<T>(q.x), static_cast<T>(q.y), static_cast<T>(q.z), static_cast<T>(q.w)) {}
constexpr vec<T,3> xyz() const { return {x,y,z}; }

template<class U, class=detail::conv_t<quat,U>> constexpr quat(const U & u) : quat(detail::convert<quat>(u)) {}
template<class U, class=detail::conv_t<U,quat>> constexpr operator U () const { return detail::convert<U>(*this); }
};

///////////////
// Constants //
///////////////

// Converts implicity to the multiplicative identity of any given algebraic object
constexpr detail::identity_t identity {1};

//////////////////////////
// Relational operators //
//////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion tests/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
sources = test-linalg.cpp test-expression-validity.cpp test-higher-order-functions.cpp test-constant-expressions.cpp test-quaternions.cpp test-scalar-accessors.cpp
sources = test-linalg.cpp test-constant-expressions.cpp test-expression-validity.cpp test-higher-order-functions.cpp test-quaternions.cpp test-scalar-accessors.cpp test-user-defined-conversions.cpp

all: test-linalg

Expand Down
1 change: 1 addition & 0 deletions tests/msvc150/linalg-test.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<ClCompile Include="..\test-quaternions.cpp" />
<ClCompile Include="..\test-linalg.cpp" />
<ClCompile Include="..\test-scalar-accessors.cpp" />
<ClCompile Include="..\test-user-defined-conversions.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\linalg.h" />
Expand Down
1 change: 1 addition & 0 deletions tests/msvc150/linalg-test.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ClCompile Include="..\test-quaternions.cpp" />
<ClCompile Include="..\test-linalg.cpp" />
<ClCompile Include="..\test-scalar-accessors.cpp" />
<ClCompile Include="..\test-user-defined-conversions.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\linalg.h" />
Expand Down
45 changes: 0 additions & 45 deletions tests/test-linalg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,47 +397,6 @@ TEST_CASE_TEMPLATE("mat<T,M,N>'s scalar constructor initializes its columns to t
}
}

////////////////////////////////////////////
// Test semantics of pointer constructors //
////////////////////////////////////////////

TEST_CASE_TEMPLATE("vec<T,M>'s pointer constructor initializes its elements in order from a pointer to contiguous elements in memory", T, arithmetic_types)
{
random_number_generator rng;
for(int i=0; i<reps; ++i)
{
const T a[4] {rng, rng, rng, rng};
const T * const p = a;
CHECK(linalg::vec<T,2>(p) == linalg::vec<T,2>(a[0], a[1]));
CHECK(linalg::vec<T,3>(p) == linalg::vec<T,3>(a[0], a[1], a[2]));
CHECK(linalg::vec<T,4>(p) == linalg::vec<T,4>(a[0], a[1], a[2], a[3]));
}
}

TEST_CASE_TEMPLATE("mat<T,M,N>'s pointer constructor initializes its elements in column-major order from a pointer to contiguous elements in memory", T, arithmetic_types)
{
random_number_generator rng;
for(int i=0; i<reps; ++i)
{
const T a[16] {rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng, rng};
const T * const p = a;

CHECK(linalg::mat<T,2,2>(p) == linalg::mat<T,2,2>({a[0],a[1]}, {a[2],a[3]}));
CHECK(linalg::mat<T,2,3>(p) == linalg::mat<T,2,3>({a[0],a[1]}, {a[2],a[3]}, {a[4],a[5]}));
CHECK(linalg::mat<T,2,4>(p) == linalg::mat<T,2,4>({a[0],a[1]}, {a[2],a[3]}, {a[4],a[5]}, {a[6],a[7]}));

CHECK(linalg::mat<T,3,2>(p) == linalg::mat<T,3,2>({a[0],a[1],a[2]}, {a[3],a[4],a[5]}));
CHECK(linalg::mat<T,3,3>(p) == linalg::mat<T,3,3>({a[0],a[1],a[2]}, {a[3],a[4],a[5]}, {a[6],a[7],a[8]}));
CHECK(linalg::mat<T,3,4>(p) == linalg::mat<T,3,4>({a[0],a[1],a[2]}, {a[3],a[4],a[5]}, {a[6],a[7],a[8]}, {a[9],a[10],a[11]}));

CHECK(linalg::mat<T,4,2>(p) == linalg::mat<T,4,2>({a[0],a[1],a[2],a[3]}, {a[4],a[5],a[6],a[7]}));
CHECK(linalg::mat<T,4,3>(p) == linalg::mat<T,4,3>({a[0],a[1],a[2],a[3]}, {a[4],a[5],a[6],a[7]}, {a[8],a[9],a[10],a[11]}));
CHECK(linalg::mat<T,4,4>(p) == linalg::mat<T,4,4>({a[0],a[1],a[2],a[3]}, {a[4],a[5],a[6],a[7]}, {a[8],a[9],a[10],a[11]}, {a[12],a[13],a[14],a[15]}));
}
}

// TODO: Appending constructors

//////////////////////////////////////////
// Test semantics of operator overloads //
//////////////////////////////////////////
Expand Down Expand Up @@ -969,13 +928,11 @@ TEST_CASE( "templates instantiate correctly" )
// Declare some variables to test functions requiring an lvalue
const float2 cf2; const float3 cf3; const float4 cf4;
float2 f2; float3 f3; float4 f4; int2 i2; int3 i3; int4 i4;
float fs[] = {1,2,3,4};

// Exercise vec<T,2>
MATCH(float2, float2());
MATCH(float2, float2(1,2) );
MATCH(float2, float2(5) );
MATCH(float2, float2(fs) );
MATCH(float2, float2(int2(3,4)) );
MATCH(const float&, cf2[1] );
MATCH(float&, f2[1] );
Expand All @@ -985,7 +942,6 @@ TEST_CASE( "templates instantiate correctly" )
MATCH(float3, float3(1,2,3) );
MATCH(float3, float3(float2(),4) );
MATCH(float3, float3(5) );
MATCH(float3, float3(fs) );
MATCH(float3, float3(int3(3,4,5)) );
MATCH(const float&, cf3[1] );
MATCH(float&, f3[1] );
Expand All @@ -996,7 +952,6 @@ TEST_CASE( "templates instantiate correctly" )
MATCH(float4, float4(1,2,3,4) );
MATCH(float4, float4(float3(),4) );
MATCH(float4, float4(5) );
MATCH(float4, float4(fs) );
MATCH(float4, float4(int4(3,4,5,6)) );
MATCH(const float&, cf4[1] );
MATCH(float&, f4[1] );
Expand Down
Loading

0 comments on commit dd95a16

Please sign in to comment.