Skip to content

Instantly share code, notes, and snippets.

@Peter0x44
Last active August 27, 2025 14:36
Show Gist options
  • Select an option

  • Save Peter0x44/cb65b9b500db4f351d7e70513f12dcb1 to your computer and use it in GitHub Desktop.

Select an option

Save Peter0x44/cb65b9b500db4f351d7e70513f12dcb1 to your computer and use it in GitHub Desktop.
Accompanies vector2-compatible-types blog post.
#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