Концепт и дружественные операторы класса

Рейтинг: 8Ответов: 1Опубликовано: 19.05.2023

Делал одну студенческую работу, вытащил делавшийся в свое время старый класс для работы с полиномами. Сам не люблю простыни, но ужать код оказалось сложно, простите уж... все работает на ура.

Решил выпендриться, и добавить концепты для ограничения "математическими" типами. Написал было

template<typename T>
requires is_integral_v<T>||is_floating_point_v<T>
class Polynome

Опять же, все работает... Но если работать с комплексными числами, ограничения не соблюдаются. Решил, недолго мудрствуя, написать простое ограничение с использованием нужных мне арифметических операций.

template<typename T, typename V>
concept PolyAble = requires(T a, V b){ a + b; a * b; };

template<typename T>
requires PolyAble<T,T>
class Polynome

И все бы ничего — если не писать умножение. Если его написать, то сразу все слетает на реализации дружественных операторов

Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& a, const V& x)
Polynome<std::common_type_t<U,V>> operator*(const V& x, const Polynome<U>& a)

В обоих случаях ругань в конечном счете доходит до того, что, мол, не получается просчитать PolyAble<Polynome<double>,Polynome<double>>.

Вот и вопрос: во-первых, не понимаю, почему он вообще выходит на эти типы, а во-вторых, как правильно указывать (если это вообще нужно в такой ситуации) требования к шаблонным свободным функциям — мне казалось, что раз они используют основной класс с ограничителем, то там он и сработает.

Вот вся простынь. Компилятор Visual C++ 2022, 17.6.0.

template<typename T>
    concept PolyAble = requires(T a, T b){ a+b; a*b; };

template<typename T>
//requires is_integral_v<T>||is_floating_point_v<T>
requires PolyAble<T>
class Polynome
{
public:
    Polynome(size_t n = 0):a(n+1,0){}
    Polynome(std::initializer_list<T> cfs)
    {
        a.insert(a.end(),rbegin(cfs),rend(cfs));
        norm();
    }
    ~Polynome() {}

    Polynome(const Polynome<T>&)            = default;
    Polynome& operator=(const Polynome<T>&) = default;
    Polynome(Polynome<T>&&)                 = default;
    Polynome& operator=(Polynome<T>&&)      = default;


    size_t extent() { return a.size()-1; }
    T  operator[](size_t i) const { return (i >= a.size()) ? T(0) : a[i]; }
    std::vector<T> coeffs() const { return a; }
    template<typename U>
        auto operator()(const U& x) -> std::common_type_t<U,T>
        {
            std::common_type_t<U,T> res = std::common_type_t<U,T>();
            for(auto c = a.rbegin(); c != a.rend(); ++c)
            {
                res = res*x + *c;
            }
            return res;
        }


    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator+(const Polynome<U>& a, const Polynome<V>& b);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator-(const Polynome<U>& a, const Polynome<V>& b);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& a, const V& x);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const V& x, const Polynome<U>& a);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& p, const Polynome<V>& q);

    template<typename U>
    friend std::ostream& operator<<(std::ostream&,const Polynome<U>&);

private:
    std::vector<T> a;
    void norm() {
        for(auto i = a.rbegin(); i != a.rend(); i = a.rbegin())
            if (*i == T(0)) a.erase(i.base()-1); else break;
        if (a.empty()) a.push_back(T{});
    }
};

template<typename T>
std::ostream& operator<<(std::ostream&os,const Polynome<T>&p)
{
    os << "(";
    for(auto i = p.a.rbegin(); i != p.a.rend(); ) { os << *i; if (++i != p.a.rend()) os << ","; }
    return os << ")";
}


template<typename U, typename V>
Polynome<std::common_type_t<U,V>> operator+(const Polynome<U>& a, const Polynome<V>& b)
{
    size_t N = std::max(a.a.size(),b.a.size());
    Polynome<std::common_type_t<U,V>> p(N-1);
    for(size_t i = 0; i < N; ++i) p.a[i] = a[i] + b[i];
    p.norm();
    return p;
}


template<typename U, typename V>
Polynome<std::common_type_t<U,V>> operator-(const Polynome<U>& a, const Polynome<V>& b)
{
    size_t N = std::max(a.a.size(),b.a.size());
    Polynome<std::common_type_t<U,V>> p(N-1);
    for(size_t i = 0; i < N; ++i) p.a[i] = a[i] - b[i];
    for(auto i = p.a.rbegin(); i != p.a.rend(); i = p.a.rbegin())
        if (*i == std::common_type_t<U,V>{}) p.a.erase(i.base()-1); else break;
    p.norm();
    return p;
}


template<typename U, typename V>
Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& a, const V& x)
{
    Polynome<std::common_type_t<U,V>> p(a.a.size()-1);
    for(size_t i = 0; i < a.a.size(); ++i) p.a[i] = a.a[i]*x;
    p.norm();
    return p;
}

template<typename U, typename V>
Polynome<std::common_type_t<U,V>> operator*(const V& x, const Polynome<U>& a)
{
    return a*x;
}

template<typename U, typename V>
Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& p, const Polynome<V>& q)
{
    Polynome<std::common_type_t<U,V>> r(p.a.size()+q.a.size()-2);
    for(size_t i = 0; i < p.a.size(); ++i)
        for(size_t j = 0; j < q.a.size(); ++j) r.a[i+j] += p[i]*q[j];
    r.norm();
    return r;
}


using Poly = Polynome<double>;

int main(int argc, char * argv[])
{
    double x[5] = {3,4,5,6,7};
    double y[5];
    Poly org{1.,2.,3.,0.,0.,0.,-3.};
    for(int i = 0; i < 5; ++i) y[i] = org(x[i]);

    vector<Poly> L(5,Poly{1});
    for(int i = 0; i < 5; ++i)
    {
        for(int j = 0; j < 5; ++j)
            if (i != j)
                L[i] = L[i]*Poly{1,-x[j]}*(1.0/(x[i]-x[j]));
        cout << i << ": " << L[i] << endl;
    }

}

Ответы

▲ 6Принят

Из-за наличия конструктора Polynome(size_t n = 0) оказывается разрешено преобразование double -> Polynome<double>. Поэтому std::common_type_t<double, Polynome<double> > возвращает Polynome<double>.

Далее, при умножении полинома на полином, компилятор выбирает из трех вариантов:

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& a, const V& x);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const V& x, const Polynome<U>& a);

    template<typename U, typename V>
    friend Polynome<std::common_type_t<U,V>> operator*(const Polynome<U>& p, const Polynome<V>& q);

Причем, все три варианта подходят, хотя имеют разный тип результата. В частности, первый вариант возвращает Polynome< Polynome<double> >. Этот вариант легален, пока определены оба оператора (сложения и умножения) для полиномов. Почему же ругается static_assert( PolyAble<Poly> ); ? Потому что при попытке проверить наличие оператора Poly{} * Poly{}, он вновь обращается к PolyAble - рекурсия.