Home > Mobile >  Using concepts with multiple parameters as constraints to functions
Using concepts with multiple parameters as constraints to functions

Time:10-01

Define a scalar to be any arithmetic type:

template<class T>
concept scalar = std::is_arithmetic_v<T>;

A vector T of a scalar ScalarType satisfies the following:

template<class T, class ScalarType>
concept vector = std::equality_comparable<T> && scalar<ScalarType> && requires(T a, T b, ScalarType c)
{
    {a   b} -> std::same_as<T>;
    {a - b} -> std::same_as<T>;
    {a  = b} -> std::same_as<T&>;
    {a -= b} -> std::same_as<T&>;
    {c * a} -> std::same_as<T>;
    {a * c} -> std::same_as<T>;
    {a / c} -> std::same_as<T>;
    {a *= c} -> std::same_as<T&>;
    {a /= c} -> std::same_as<T&>;
    {-a} -> std::same_as<T>;
    {zero(std::declval<empty<T>>())} -> std::same_as<T>;
};

A point satisfies the following:

template<class T, class VectorType, class ScalarType>
concept point = std::equality_comparable<T>
    && vector<VectorType, ScalarType>
    && requires(T p1, T p2, VectorType v)
{
    {p1   v} -> std::same_as<T>;
    {p1 - v} -> std::same_as<T>;
    {p1  = v} -> std::same_as<T&>;
    {p1 -= v} -> std::same_as<T&>;
    {p1 - p2} -> std::same_as<VectorType>;
};

Now I want to apply point as a constraint to a function that computes the distance between two points:

template<class Vector, class Scalar, point<Vector, Scalar> P>
constexpr auto distance(P p1, P p2)
{
    return norm(p1 - p2);
}

But it is not possible to deduce Vector and Scalar given only P. First notice that

static_assert(point<int*, intptr_t, intptr_t>);

Any (non-void) pointer is a point with intptr_t as both its vector and scalar.

Trying this:

constexpr std::array<int, 20> test{};
auto d = ::distance(std::data(test), std::data(test)   std::size(test));

couldn’t deduce template parameter ‘Vector’

But if I give it the missing pieces:

auto d = distance<intptr_t, intptr_t>(std::data(test), std::data(test)   std::size(test));

Is it possible to keep Scalar and Vector as free type parameters and still get type deduction to work properly, or should I require that there are vectors and points posses using aliases that I can pick up on. In that case, point would look like

template<class T>
concept point = std::equality_comparable<T> && requires(T p1, T p2)
{
    typename T::vector_type;
    {p1   v} -> std::same_as<T>;
    {p1 - v} -> std::same_as<T>;
    {p1  = v} -> std::same_as<T&>;
    {p1 -= v} -> std::same_as<T&>;
    {p1 - p2} -> std::same_as<T::vector_type>;
};

While this works, the vector T must be designed to work with this concept. I really like the idea of affine spaces that works with std::chrono:

static_assert(point<std::chrono::steady_clock::time_point, std::chrono::steady_clock::duration, long>);

CodePudding user response:

Concepts do not (directly) interact with template argument deduction. Deduction happens, and arguments are applied to the available templates. Concepts constrain the set of available templates, such that any particular template is either available or not. A concept cannot inform which type deduction comes up with.

So if you want concepts to have those parameters (for some reason), any template constrained on those concepts will have to provide them. And therefore, if deduction cannot otherwise deduce them, those arguments will have to be explicitly provided by the caller of the function.

CodePudding user response:

By applying The fundamental theorem of software engineering, the problem can be solved by introducing an additional abstraction vector_space:

template<class T>
concept vector_space = vector<typename T::vector_type, typename T::scalar_type>;

template<class T>
concept affine_space = vector_space<T>
    && point<typename T::point_type, typename T::vector_type, typename T::scalar_type>;

Now:

struct writable_address_space
{
    using vector_type = intptr_t;
    using scalar_type = intptr_t;
    using point_type = std::byte*;
};

static_assert(affine_space<writable_address_space>);
  • Related