I have a project which is using a few libraries where each one of the libraries define some sort of 3D vector, as an example I use SFML's 3D vector in some parts of the code, reactphysics3d's Vector3
on others, and yet another 3D vector from another library.
Now I need to code the cross product and the std::ostream &operator <<
for each one of the vectors:
constexpr sf::Vector3f cross(const sf::Vector3f &a, const sf::Vector3f &b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
std::ostream &operator <<(std::ostream &o, const sf::Vector3f &v)
{
return o << '{' << v.x << ", " << v.y << ", " << v.z << '}';
}
// ... repeat for every 3D vector type
Which implies lots of code repetition, so I changed the approach:
template <typename vector3a_t, typename vector3b_t>
constexpr vector3a_t cross(const vector3a_t &a, const vector3b_t &b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
Now the cross
function two types which are expected to have members x
, y
and z
but obvuiously this isn't aplicable to std::ostream &operator <<
:
template <typename vector3_t>
std::ostream &operator <<(std::ostream &o, const vector3_t &v)
{
return o << '{' << v.x << ", " << v.y << ", " << v.z << '}';
}
because the template type vector3_t
shadows everything, so I was wondering if it is possible to constrain the function to accept any type that conforms the concept of "should look like a 3D vector":
template <typename vector_t>
concept vector3_c = requires(vector_t v) { // error: expected unqualified-id
{ std::is_scalar_v<decltype(v.x)> } -> true;
{ std::is_scalar_v<decltype(v.y)> } -> true;
{ std::is_scalar_v<decltype(v.z)> } -> true;
};
template <vector3_c A, vector3_c B>
constexpr decltype(A) cross(const A &a, const B &b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
template <vector3_c V>
std::ostream &operator <<(std::ostream &o, const V &v)
{
return o << '{' << v.x << ", " << v.y << ", " << v.z << '}';
}
But this doesn't even compile. It's my first time attempting concepts and I don't know if what I'm trying to do is even possible.
CodePudding user response:
If what you want is to check to see if a type has x,y,z members that are scalars, you can do this:
//Ensures that the user won't do something weird
template<typename T>
concept ref_to_scalar = std::is_reference_v<T> && std::is_scalar_v<std::remove_reference_t<T>>;
template<typename Vec>
concept vector_2d = requires(Vec v) {
{v.x} -> ref_to_scalar;
{v.y} -> ref_to_scalar;
};
//vec3d subsumes vec2d
template<typename Vec>
concept vector_3d = vector_2d<Vec> && requires(Vec v) {
{v.z} -> ref_to_scalar;
};
CodePudding user response:
This
template <typename vector_t>
concept vector3_c = requires(vector_t v) { // error: expected unqualified-id
{ std::is_scalar_v<decltype(v.x)> } -> true;
};
will only check the validity of the expression std::is_scalar_v<decltype(v.x)>
. In addition, the return-type-requirement constrains the type not the value, so -> true
is not correct, it should be ->std::same_as<bool>
.
You should use nested requires
to evaluate the value of the expression, for example
template<typename vector_t>
concept vector3_c = requires(vector_t v) {
requires std::is_scalar_v<decltype(v.x)> &&
std::is_scalar_v<decltype(v.y)> &&
std::is_scalar_v<decltype(v.z)>;
};
As said in the comments, if you just want to detect whether a struct has valid member variables, it is more intuitive to use a constraint expression of the form like {v.x} -> scalar
template<class T>
concept scalar = std::is_scalar_v<T>;
template<typename vector_t>
concept vector3_c = requires(vector_t v) {
{ auto(v.x) } -> scalar;
{ auto(v.y) } -> scalar;
{ auto(v.z) } -> scalar;
};
where auto(x)
is the language supported decay-copy in C 23.