Параметризованная перегрузка оператора - PullRequest
0 голосов
/ 23 июня 2019

Предположим, у нас есть тип X, значения которого мы хотим сравнить.Предположим далее, что существует не одно сравнение, а семейство сравнений, параметризованное значением другого типа T.Другими словами, давайте предположим, что нам дана функция прототипа:

bool f(T p, X v1, X v2);

Учитывая параметр p, f(p, v1, v2) должно быть истинным, если v1 сравнивает меньше чем v2 в порядкесоответствующий p.

Теперь я ищу способ перегрузить оператор < в лексической области видимости, учитывая значение p типа T, такое что v1 < v2в этой области локально скомпилирован в f(p, v1, v2).

Просто для иллюстрации следующее достигает того, чего я хочу в Схеме, где ... обозначает рассматриваемую лексическую область:

(let ((< (lambda (v1 v2) (f p v1 v2))))
  ...)

У меня есть две идеи о том, как получить почти то, что я хочу в C ++, но я не удовлетворен ими.

Первая идея состоит не в том, чтобы определить оператор сравнения между значениями типа X, а междузначения типа std::pair <T, X>.Вместо v1 < v2 нужно написать std::pair (p, v1) < std::pair (p, v2), а std::pair (p1, v2) < std::pair (p2, v2) перегружен для компиляции в f(p1, v1, v2).Проблема этого подхода заключается, например, в том, что второй параметр p2 совершенно лишний.Отказ от этого нарушает симметрию.

Моя вторая идея - использовать что-то вроде шаблонов выражений.Здесь v1 < v2 не возвращает логическое значение, а просто абстрактное выражение (дерево), которое оценивается следующим образом: p (v1 < v2), где p () соответственно перегружено.Проблема этого подхода заключается в том, что нельзя перегружать bool (v1 < v2), в частности, такие выражения, как v1 < v2 ? ... : ..., не будут компилироваться.

Ответы [ 2 ]

1 голос
/ 23 июня 2019

Я не совсем согласен с вашим вариантом использования, но, тем не менее, это интересный вопрос. Моя любимая вещь в C ++ - это то, что он дает вам так много контроля - настолько, что возможности большинства других языков можно моделировать с помощью умного C ++. Этот случай не является исключением.

Вот решение, которое я только что вместе, должно быть очень похоже на схему. Он изменяет компаратор, используемый для определенных типов и в определенных потоках на время объявления области. Я включил комментарии, чтобы объяснить все важные биты.

#include <iostream>
#include <utility>
#include <cassert>

struct vector2
{
    int x, y;

    // the type of comparator to use (function pointer)
    typedef bool(*comparator_t)(const vector2&, const vector2&);
    // gets the value of comparator we're currently using by reference (one for each thread)
    static comparator_t &comparator()
    {
        thread_local comparator_t c = nullptr;
        return c;
    }

    // define comparison operator to use comparator()
    friend bool operator<(const vector2 &a, const vector2 &b) { return comparator()(a, b); }
};

// a sentry type for changing the comparator for type T in the current scope
// it changes it back upon destruction (at end of scope)
template<typename T>
struct comparator_sentry_t
{
    typename T::comparator_t old;
    explicit comparator_sentry_t(typename T::comparator_t c) : old(std::exchange(T::comparator(), c)) {}
    ~comparator_sentry_t() { T::comparator() = old; }
};

#define _MERGE(x, y) x ## y
#define MERGE(x, y) _MERGE(x, y)

// a user-level macro which is used to change the comparator for the rest of the current scope
#define SET_COMPARATOR(type, cmp) comparator_sentry_t<type> MERGE(__comparator_sentry, __LINE__) { (cmp) }

// -- below this line is demo code -- //

// a couple of example comparison functions
bool cmp_x_less(const vector2 &a, const vector2 &b) { return a.x < b.x; }
bool cmp_x_greater(const vector2 &a, const vector2 &b) { return a.x > b.x; }

bool cmp_y_less(const vector2 &a, const vector2 &b) { return a.y < b.y; }
bool cmp_y_greater(const vector2 &a, const vector2 &b) { return a.y > b.y; }

// some functions to demonstrate this works across function invocations
void foo(const vector2 &a, const vector2 &b)
{
    assert(a < b);
    SET_COMPARATOR(vector2, cmp_y_greater);
}
void bar(const vector2 &a, const vector2 &b)
{
    assert(b < a);
    SET_COMPARATOR(vector2, cmp_x_less);
}

int main()
{
    vector2 a{ 1, 3 };
    vector2 b{ 2, 6 };

    SET_COMPARATOR(vector2, cmp_x_less);
    SET_COMPARATOR(vector2, cmp_x_less); // redeclaring in same scope is ok

    assert(a < b);
    foo(a, b);     // changes comparator internally
    assert(a < b); // demonstrate that said change is reverted at end of function

    {
        // change comparator for this scope
        SET_COMPARATOR(vector2, cmp_y_greater);

        assert(b < a);
        bar(a, b);
        assert(b < a);
    }

    assert(a < b); // demonstrate the comparator change was reverted
    foo(a, b);
    assert(a < b);

    return 0;
}
1 голос
/ 23 июня 2019

Что-то вроде этого будет работать:

#include <iostream>

struct PointA {
    int x, y;
    static bool(*compareLarger)(PointA const&, PointA const&);
    bool
    operator >(PointA const& rhs) const {
        return compareLarger(*this, rhs);
    }
};

bool(*PointA::compareLarger)(PointA const&, PointA const&) = nullptr;

bool
compareX(PointA const& lhs, PointA const& rhs) {
    return lhs.x > rhs.x;
}

bool
compareY(PointA const& lhs, PointA const& rhs) {
    return lhs.y > rhs.y;
}

int
main(int, char**) {
    PointA p1{0, 1}, p2{1, 0};
    PointA::compareLarger = compareX;
    if (p1 > p2) std::cout << "P1 is larger\n";
    else std::cout << "P2 is larger\n";
    PointA::compareLarger = compareY;
    if (p1 > p2) std::cout << "P1 is larger\n";
    else std::cout << "P2 is larger\n";
    return 0;
}

Это то, о чем вы думаете?

РЕДАКТИРОВАТЬ:

Это даже разрешило бы это время компиляции,если это важно:

#include <iostream>

struct PointA {
    int x, y;
};

namespace NS1 {
    bool
    operator>(PointA const& lhs, PointA const& rhs) {
        return lhs.x > rhs.x;
    }
}

namespace NS2 {
    bool
    operator>(PointA const& lhs, PointA const& rhs) {
        return lhs.y > rhs.y;
    }
}

int
main(int, char**) {
    PointA p1{0, 1}, p2{1, 0};
    {
        using NS1::operator >;
        if (p1 > p2) std::cout << "P1 is larger\n";
        else std::cout << "P2 is larger\n";
    }
    {
        using NS2::operator >;
        if (p1 > p2) std::cout << "P1 is larger\n";
        else std::cout << "P2 is larger\n";
    }
    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...