Problem z definicją szablonu funkcji zaprzyjaźnionej

0

Witajcie,
w ramach ogromnej nudy, klepię sobie własną bibliotekę do obsługi liczb zespolonych.

Mam klasę:

template <typename T = double>
    class Complex {
    ...
    friend std::ostream& operator <<(std::ostream& os, const Complex<T>& c) ;
   ...
}

Jak teraz udostępnić definicję tej funkcji na zewnętrz deklaracji?

Cos takiego:

template <typename T>
    std::ostream& operator <<(std::ostream& os, const Complex<T>& c) {
        return os << c.realNum <<  " " << c.sign << " " << std::abs(c.imaginaryNum) << 'i';
    }

powoduje błąd linkera. Teoretycznie rozwiązaniem jest umieszczenie definicji w deklaracji, jednak chciałbym poznać sposób, dzięki któremu będzie to działało na zewnątrz definicji.

0

Rozwiązanie się znalazło:

    template <typename T = double> class Complex;

    template <typename T>
    std::ostream& operator <<(std::ostream& os, const Complex<T>& c);

    template <typename T>
    class Complex
    {
    ...
    friend std::ostream& operator << <>(std::ostream& os, const Complex& c) ;
    ...
template <typename T>
    std::ostream& operator <<(std::ostream& os, const Complex<T>& c) {
        return os << c.realNum <<  " " << c.sign << " " << std::abs(c.imaginaryNum) << 'i';
    }

Czyli rozumiem, że bez wcześniejszej deklaracji, tak jak tutaj - nie ma możliwości zrealizowania tego?

1

Ja definiuje operatory strumieniowe bez przyjaźni.
Po prostu defunije sobie normalną metodę, a potem wywołuję z operatora:

template <typename T = double>
class Complex {
public:
     std::ostream &printTo(std::ostream &) const;
};

template <typename T>
inline std::ostream& operator <<(std::ostream& os, const Complex<T>& c) {
    return c.printTo(os);
}
1

friend w tym miejscu nie wydaje się być dobrym pomysłem. Dlaczego funkcja, która ma zaprezentować w jakiś sposób obiekt ma mieć dostęp do prywatnych danych klasy? Nie powinna przecież korzystać z niczego, co nie jest publicznie dostępne. To dziwna sytuacja, kiedy jakąś daną w klasie możemy podejrzeć tylko wypisując ją na stdout.
Ponadto, jeśli friend funkcja korzysta ze szczegółów implementacji klasy, to może być to problem w przyszłości - implementacje się zmieniają, co wymusi na Tobie refactoring dodatkowych funkcji. API klasy (jej publiczne funkcje, dane) zmienia się nigdy albo prawie nigdy. I z tego powinieneś korzystać.
Podsumowując - friend to zło.

0

Na ten moment nie widzę niestety alternatywy dla czegoś takiego:

friend Complex operator*(T number, const Complex& c) noexcept {
             return c * number;
        }

jeżeli zamiast friend zrobię funkcję na zewnątrz klasy szablonowej, nie będę miał możliwości automatycznej konwersji, i np. 5 * c nie zadziała, ale już 5.0 * c tak.

0

po co ci przyjaźń w tym wypadku?
Pomieszałeś, też użycie parametru szablonu.

template <typename T>
inline Complex<T> operator*(T number, const Complex<T>& c) noexcept {
    return c * number;
}
0

Jak to po co? Jeżeli użyję friend mogę napisać 5.0 * c.

Twoje rozwiązanie nie działa, bo nie dokonuje mi się automatyczna konwersja, np. int na double, o czym już pisałem.

Obszedłem to w inny sposób:

template <typename T1, typename T2>
Complex<T2> operator*(T1 number, const Complex<T2>& c) noexcept {
          return c * number;
}

W którym miejscu pomieszałem parametr?

0

Nie no skoro twoja implementacja polegała jedynie na odwróceniu kolejności argumentów, to byłem przekonany, że masz operator mnożenia dla tej kolejności argumentów, np tak:

template <typename T = double>
class Complex {
public:
     Complex(T aRe, T, aIm) : re(aRe), im(aIm) {}

     Complex<T> operator*(T x) const {
            return { re * x, im * x }; // fyi C++11
     }
};

Moja magiczna kula pokazywała, że masz taki operator już zdefiniowany, więc ten dodatkowy powinien działać.

0

No właśnie w tym rzecz, że miałem taki operator, stąd odwrócenie kolejności w operatorze.

template <typename Type>
    auto Complex<Type>::operator *(Type number) const noexcept {
        return Complex{realNum * number, imaginaryNum * number};
    }

tak wygląda ten operator. Niestety mimo tego, nie zachodzi promocja int na double.

0

możesz zerknąć w sumie do całego kodu - pewnie ostry overengineering, ale taki miał właśnie być, bawię się językiem.
https://github.com/Tenonymous/complex/blob/master/complex.h

ps: jak w międzyczasie natrafisz na jakiś błąd, daj znać, chętnie poprawię ;)

2

Poprawiłem parę rzeczy.
Nie wiem po co definiujesz metody poza klasą? I tak wszystko musi być w pliku nagłówkowym.
Poza tym ten nadmiar "dekoracji" jest bardzo irytujący.

#ifndef COMPLEX_H
#define COMPLEX_H

#include <cmath>
#include <iostream>

namespace cmpx {
    template <typename Type>
    class Complex
    {
    public:
        using value_type = Type;

        constexpr           Complex(const Type& real = {}, const Type& imaginary = {})   noexcept;
                            ~Complex()                                                 = default;

        constexpr           Complex(Complex<Type>&&)                                         = default;
        constexpr           Complex<Type>& operator =(Complex<Type>&&)                             = default;

        constexpr           Complex(const Complex<Type>&)                                    = default;
        constexpr           Complex<Type>& operator =(const Complex<Type>&)                        = default;

        auto                operator +(const Complex<Type>&)                                   const noexcept;
        auto&               operator +=(const Complex<Type>&)                                  noexcept;
        auto                operator -(const Complex<Type>&)                                   const noexcept;
        auto&               operator -=(const Complex<Type>&)                                  noexcept;
        auto                operator *(value_type number)                                      const noexcept;
        auto&               operator *=(value_type number)                                     noexcept;
        Complex<Type> operator *(const Complex<Type>& b) const noexcept {
            return {
                this->realNumber() * b.realNumber() - this->imaginaryNumber() * b.imaginaryNumber(),
                this->realNumber() * b.imaginaryNumber() + this->imaginaryNumber() * b.realNumber()
            };
        }

        constexpr bool      operator ==(const Complex<Type>&)                                  const noexcept;
        constexpr bool      operator !=(const Complex<Type>&)                                  const noexcept;

        constexpr Type      realNumber()                                                 const noexcept { return realNum;}
        constexpr Type      imaginaryNumber()                                            const noexcept { return imaginaryNum;}
        constexpr double    module()                                                     const noexcept;
        constexpr auto      operator -()                                                 const noexcept;

        auto&               print(std::ostream& os)                                      const;
        auto&               scanner(std::istream&);

    private:
        Type                realNum;
        Type                imaginaryNum;
    };

    template <typename Type>
    constexpr Complex<Type>::Complex(const Type& real, const Type& imaginary) noexcept
        : realNum{real}, imaginaryNum{imaginary}
    {}

    template <typename Type>
    auto Complex<Type>::operator +(const Complex<Type>& c) const noexcept {
        return Complex<Type>{realNum + c.realNum, imaginaryNum + c.imaginaryNum};
    }

    template <typename Type>
    auto& Complex<Type>::operator +=(const Complex<Type>& c) noexcept {
        realNum += c.realNum;
        imaginaryNum += c.imaginaryNum;
        return *this;
    }

    template <typename Type>
    auto Complex<Type>::operator -(const Complex<Type>& c) const noexcept {
        return Complex<Type>{realNum - c.realNum, imaginaryNum - c.imaginaryNum};
    }

    template <typename Type>
    auto& Complex<Type>::operator -=(const Complex<Type>& c) noexcept {
        realNum -= c.realNum;
        imaginaryNum -= c.imaginaryNum;
        return *this;
    }

    template <typename Type>
    auto Complex<Type>::operator *(Complex<Type>::value_type number) const noexcept {
        return Complex<Type>{realNum * number, imaginaryNum * number};
    }

    template <typename Type>
    auto& Complex<Type>::operator *=(Complex<Type>::value_type number) noexcept {
        realNum *= number;
        imaginaryNum *= number;
        return *this;
    }

    template <typename Type>
    constexpr double Complex<Type>::module() const noexcept {
        return sqrt(realNum * realNum + imaginaryNum * imaginaryNum);
    }

    template <typename Type>
    constexpr auto Complex<Type>::operator -() const noexcept {
        return Complex<Type>{realNum, -imaginaryNum};
    }

    template <typename Type>
    auto& Complex<Type>::print(std::ostream& os) const {
        os << realNum;
        if (imaginaryNum != 0.0) {
            auto oldFlags = os.flags();
            os.setf(std::ios::showpos);
            os << imaginaryNum << 'i';
            os.flags(oldFlags);
        }
        return os;
    }

    template <typename Type>
    auto& Complex<Type>::scanner(std::istream& is) {
        char ch;
        is >> realNum >> ch >> imaginaryNum;
        if (ch == '-') {
            imaginaryNum = -imaginaryNum;
        }
        return is;
    }

    template <typename Type>
    std::ostream& operator <<(std::ostream& os, const Complex<Type>& c) {
        return c.print(os);
    }

    template <typename Type>
    std::istream& operator >>(std::istream& is, Complex<Type>& c) {
        return c.scanner(is);
    }

    template <typename Type>
    auto operator*(typename Complex<Type>::value_type number, const Complex<Type>& c) noexcept {
        return c * number;
    }

    template <typename Type>
    constexpr bool Complex<Type>::operator ==(const Complex<Type>& c) const noexcept {
        return realNum == c.realNum && imaginaryNum == c.imaginaryNum;
    }

    template <typename Type>
    constexpr bool Complex<Type>::operator !=(const Complex<Type>& c) const noexcept {
        return !(*this == c);
    }
}
#endif // COMPLEX_H
0

Na poprawki zerknę jutro. Odnośnie pytań:

  1. Dla czytelności, przyzwyczaiłem się - tak mi wygodniej.
  2. Konkretniej? O jakie dekoracje chodzi? Jeżeli mowa, o np. pisaniu auto w typie zwrotnym funkcji - jak mówiłem, zabawa językiem i przyjęta konwencja, tj. dla typów wbudowanych, np double, piszę typ, dla klas, etc używam auto tam gdzie tylko mogę.
1

FYI std::istream& operator >>(std::istream& is, Complex<Type>& c) coś nie chce działać:
https://wandbox.org/permlink/fAEWWXDYd35AjSlj

A co ciekawe powinno działać:
https://wandbox.org/permlink/GeXX9PWvN7kVqUDP

0

Jest na to jakieś logiczne wyjasnienie?

1 użytkowników online, w tym zalogowanych: 0, gości: 1