Last active
August 27, 2025 14:36
-
-
Save Peter0x44/cb65b9b500db4f351d7e70513f12dcb1 to your computer and use it in GitHub Desktop.
Accompanies vector2-compatible-types blog post.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #if 0 | |
| # Universal Vector2 Type Implementation - Execute this file with sh to build and test it | |
| # | |
| # This file demonstrates a template-based approach to creating a Vector2 type | |
| # that can seamlessly convert to and from other Vector2-like types from different | |
| # libraries (Box2D, raylib, etc.) without knowing about them in advance. | |
| # | |
| # | |
| CXX=${CXX:-c++} | |
| echo "=== Testing C++20 version ===" | |
| $CXX -std=c++20 -DTEST -o test_cpp20.exe $0 && echo "C++20 compiling succeeded; testing" && ./test_cpp20.exe || echo "C++20 compiling failed" | |
| echo "" | |
| echo "=== Testing C++17 version ===" | |
| $CXX -std=c++17 -DTEST -o test_cpp17.exe $0 && echo "C++17 compiling succeeded; testing" && ./test_cpp17.exe || echo "C++17 compiling failed" | |
| echo "" | |
| echo "=== Testing C++11 version ===" | |
| $CXX -std=c++11 -DTEST -o test_cpp11.exe $0 && echo "C++11 compiling succeeded; testing" && ./test_cpp11.exe || echo "C++11 compiling failed" | |
| ./test_cpp11.exe | |
| echo "" | |
| rm -f test_cpp20.exe test_cpp17.exe test_cpp11.exe | |
| exit 0 | |
| #endif | |
| #include <type_traits> | |
| #include <iostream> | |
| #if __cplusplus >= 202002L | |
| #include <concepts> | |
| #endif | |
| typedef struct { float x; float y;} b2Vec2; | |
| typedef struct { float x; float y;} Vector2; | |
| // Additional test types to demonstrate the template solution | |
| struct Point2D { float x, y; }; | |
| struct Vec2f { float x, y; }; | |
| #if __cplusplus >= 202002L | |
| // ============================================================================ | |
| // C++20 CONCEPTS VERSION | |
| // ============================================================================ | |
| // C++20 concept to check if a type has x,y members convertible to float | |
| template<typename T> | |
| concept HasXYMembers = requires(T t) { | |
| { t.x } -> std::convertible_to<float>; | |
| { t.y } -> std::convertible_to<float>; | |
| }; | |
| // C++20 concept to check if a type has x,y members that are float | |
| template<typename T> | |
| concept HasFloatXYMembers = requires(T t) { | |
| requires std::same_as<decltype(t.x), float>; | |
| requires std::same_as<decltype(t.y), float>; | |
| }; | |
| // C++20 concept to check if a type can be constructed from two floats | |
| template<typename T> | |
| concept ConstructibleFromXY = requires(float x, float y) { | |
| T{x, y}; | |
| }; | |
| // Concept for outgoing conversions (allows any convertible type) | |
| template<typename T> | |
| concept CompatibleVec2Out = HasXYMembers<T> && | |
| ConstructibleFromXY<T>; | |
| // Concept for incoming conversions (only allows floats) | |
| template<typename T> | |
| concept CompatibleVec2In = HasFloatXYMembers<T> && | |
| ConstructibleFromXY<T>; | |
| #elif __cplusplus >= 201703L | |
| // ============================================================================ | |
| // C++17 TYPE TRAITS VERSION | |
| // ============================================================================ | |
| // Type trait to detect if a type has x and y members of compatible types | |
| template<typename T, typename = void> | |
| struct has_xy_members : std::false_type {}; | |
| template<typename T> | |
| struct has_xy_members<T, std::void_t< | |
| decltype(std::declval<T>().x), | |
| decltype(std::declval<T>().y) | |
| >> : std::conjunction< | |
| std::is_convertible<decltype(std::declval<T>().x), float>, | |
| std::is_convertible<decltype(std::declval<T>().y), float> | |
| > {}; | |
| // Type trait to detect if a type has x and y members that are floats | |
| template<typename T, typename = void> | |
| struct has_float_xy_members : std::false_type {}; | |
| template<typename T> | |
| struct has_float_xy_members<T, std::void_t< | |
| decltype(std::declval<T>().x), | |
| decltype(std::declval<T>().y) | |
| >> : std::conjunction< | |
| std::is_same<decltype(std::declval<T>().x), float>, | |
| std::is_same<decltype(std::declval<T>().y), float> | |
| > {}; | |
| // Type trait to detect if a type can be constructed from two floats | |
| template<typename T, typename = void> | |
| struct is_constructible_from_xy : std::false_type {}; | |
| template<typename T> | |
| struct is_constructible_from_xy<T, std::void_t< | |
| decltype(T{std::declval<float>(), std::declval<float>()}) | |
| >> : std::true_type {}; | |
| // Trait for outgoing conversions (allows any convertible type) | |
| template<typename T> | |
| struct is_compatible_vec2_out : std::conjunction< | |
| has_xy_members<T>, | |
| is_constructible_from_xy<T> | |
| > {}; | |
| // Trait for incoming conversions (only allow floats) | |
| template<typename T> | |
| struct is_compatible_vec2_in : std::conjunction< | |
| has_float_xy_members<T>, | |
| is_constructible_from_xy<T> | |
| > {}; | |
| template<typename T> | |
| constexpr bool is_compatible_vec2_out_v = is_compatible_vec2_out<T>::value; | |
| template<typename T> | |
| constexpr bool is_compatible_vec2_in_v = is_compatible_vec2_in<T>::value; | |
| #else | |
| // ============================================================================ | |
| // C++11 VERSION | |
| // ============================================================================ | |
| // C++11 version of void_t | |
| template<typename...> | |
| using void_t = void; | |
| // C++11 version of conjunction | |
| template<typename...> | |
| struct conjunction : std::true_type {}; | |
| template<typename T> | |
| struct conjunction<T> : T {}; | |
| template<typename T, typename... Ts> | |
| struct conjunction<T, Ts...> : std::conditional<bool(T::value), conjunction<Ts...>, T>::type {}; | |
| // C++11 version of negation | |
| template<typename T> | |
| struct negation : std::integral_constant<bool, !bool(T::value)> {}; | |
| // Type trait to detect if a type has x and y members of compatible types | |
| template<typename T, typename = void> | |
| struct has_xy_members : std::false_type {}; | |
| template<typename T> | |
| struct has_xy_members<T, void_t< | |
| decltype(std::declval<T>().x), | |
| decltype(std::declval<T>().y) | |
| >> : conjunction< | |
| std::is_convertible<decltype(std::declval<T>().x), float>, | |
| std::is_convertible<decltype(std::declval<T>().y), float> | |
| > {}; | |
| // Type trait to detect if a type has x and y members that are floats | |
| template<typename T, typename = void> | |
| struct has_float_xy_members : std::false_type {}; | |
| template<typename T> | |
| struct has_float_xy_members<T, void_t< | |
| decltype(std::declval<T>().x), | |
| decltype(std::declval<T>().y) | |
| >> : conjunction< | |
| std::is_same<decltype(std::declval<T>().x), float>, | |
| std::is_same<decltype(std::declval<T>().y), float> | |
| > {}; | |
| // Type trait to detect if a type can be constructed from two floats | |
| template<typename T, typename = void> | |
| struct is_constructible_from_xy : std::false_type {}; | |
| template<typename T> | |
| struct is_constructible_from_xy<T, void_t< | |
| decltype(T{std::declval<float>(), std::declval<float>()}) | |
| >> : std::true_type {}; | |
| // Trait for outgoing conversions (allows any convertible type) | |
| template<typename T> | |
| struct is_compatible_vec2_out : conjunction< | |
| has_xy_members<T>, | |
| is_constructible_from_xy<T> | |
| > {}; | |
| // Trait for incoming conversions (only allows floats) | |
| template<typename T> | |
| struct is_compatible_vec2_in : conjunction< | |
| has_float_xy_members<T>, | |
| is_constructible_from_xy<T> | |
| > {}; | |
| // C++11 doesn't have variable templates, so we use function templates | |
| template<typename T> | |
| constexpr bool is_compatible_vec2_out_v() { return is_compatible_vec2_out<T>::value; } | |
| template<typename T> | |
| constexpr bool is_compatible_vec2_in_v() { return is_compatible_vec2_in<T>::value; } | |
| #endif | |
| struct MyVec2 { | |
| float x, y; | |
| MyVec2(float x = 0.0f, float y = 0.0f) : x(x), y(y) {} | |
| #if __cplusplus >= 202002L | |
| // Template converting constructor - only allows floats | |
| template<CompatibleVec2In T> | |
| MyVec2(const T& other) : x(other.x), y(other.y) {} | |
| // C++20 version using concepts - allows any convertible type | |
| template<CompatibleVec2Out T> | |
| operator T() const { | |
| return T{x, y}; | |
| } | |
| #elif __cplusplus >= 201703L | |
| // Template converting constructor - only allows floats | |
| template<typename T> | |
| MyVec2(const T& other) : x(other.x), y(other.y) { | |
| static_assert(is_compatible_vec2_in_v<T>, | |
| "Source type must have float x,y members and be constructible from (float, float)"); | |
| } | |
| // C++17 version using SFINAE and type traits | |
| template<typename T> | |
| operator T() const { | |
| static_assert(is_compatible_vec2_out_v<T>, | |
| "Target type must have x,y members and be constructible from (float, float)"); | |
| return T{x, y}; | |
| } | |
| #else | |
| // Template converting constructor - only allows floats | |
| template<typename T> | |
| MyVec2(const T& other) : x(other.x), y(other.y) { | |
| static_assert(is_compatible_vec2_in_v<T>(), | |
| "Source type must have float x,y members and be constructible from (float, float)"); | |
| } | |
| // C++11 version using SFINAE and type traits - allows any convertible type | |
| template<typename T> | |
| operator T() const { | |
| static_assert(is_compatible_vec2_out_v<T>(), | |
| "Target type must have x,y members and be constructible from (float, float)"); | |
| return T{x, y}; | |
| } | |
| #endif | |
| }; | |
| #ifdef TEST | |
| // Test functions to demonstrate the conversions | |
| void test_b2Vec2(b2Vec2 v) { | |
| std::cout << "b2Vec2: (" << v.x << ", " << v.y << ")\n"; | |
| } | |
| void test_Vector2(Vector2 v) { | |
| std::cout << "Vector2: (" << v.x << ", " << v.y << ")\n"; | |
| } | |
| void test_Point2D(Point2D p) { | |
| std::cout << "Point2D: (" << p.x << ", " << p.y << ")\n"; | |
| } | |
| void test_Vec2f(Vec2f v) { | |
| std::cout << "Vec2f: (" << v.x << ", " << v.y << ")\n"; | |
| } | |
| int main() { | |
| // Display which C++ version is being used | |
| #if __cplusplus >= 202002L | |
| std::cout << "Using C++20 concepts version!\n"; | |
| #elif __cplusplus >= 201703L | |
| std::cout << "Using C++17 type traits version!\n"; | |
| #else | |
| std::cout << "Using C++11 compatibility version!\n"; | |
| #endif | |
| std::cout << "C++ standard: " << __cplusplus << "\n\n"; | |
| MyVec2 myVec(3.14f, 2.71f); | |
| // Test outgoing conversions (MyVec2 -> other types) | |
| std::cout << "=== Outgoing Conversions (MyVec2 -> other types) ===\n"; | |
| test_b2Vec2(myVec); // Converts to b2Vec2 | |
| test_Vector2(myVec); // Converts to Vector2 | |
| test_Point2D(myVec); // Converts to Point2D | |
| test_Vec2f(myVec); // Converts to Vec2f | |
| // Direct assignment also works | |
| b2Vec2 b = myVec; | |
| Vector2 v = myVec; | |
| Point2D p = myVec; | |
| Vec2f vf = myVec; | |
| std::cout << "Direct assignments work too!\n"; | |
| std::cout << "b2Vec2: (" << b.x << ", " << b.y << ")\n"; | |
| std::cout << "Vector2: (" << v.x << ", " << v.y << ")\n"; | |
| std::cout << "Point2D: (" << p.x << ", " << p.y << ")\n"; | |
| std::cout << "Vec2f: (" << vf.x << ", " << vf.y << ")\n\n"; | |
| // Test incoming conversions (other types -> MyVec2) | |
| std::cout << "=== Incoming Conversions (other types -> MyVec2) ===\n"; | |
| b2Vec2 source1 = {10.0f, 20.0f}; | |
| Vector2 source2 = {30.0f, 40.0f}; | |
| Point2D source3 = {50.0f, 60.0f}; | |
| Vec2f source4 = {70.0f, 80.0f}; | |
| MyVec2 result1 = source1; // b2Vec2 -> MyVec2 | |
| MyVec2 result2 = source2; // Vector2 -> MyVec2 | |
| MyVec2 result3 = source3; // Point2D -> MyVec2 | |
| MyVec2 result4 = source4; // Vec2f -> MyVec2 | |
| std::cout << "b2Vec2 -> MyVec2: (" << result1.x << ", " << result1.y << ")\n"; | |
| std::cout << "Vector2 -> MyVec2: (" << result2.x << ", " << result2.y << ")\n"; | |
| std::cout << "Point2D -> MyVec2: (" << result3.x << ", " << result3.y << ")\n"; | |
| std::cout << "Vec2f -> MyVec2: (" << result4.x << ", " << result4.y << ")\n"; | |
| std::cout << "\nBidirectional conversion working perfectly!\n"; | |
| return 0; | |
| } | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment