Namespaces
Variants

operator overloading

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

사용자 정의 타입의 피연산자에 대한 C++ 연산자를 사용자 정의합니다.

목차

구문

연산자 함수 는 특별한 함수 이름을 가진 함수 입니다:

operator op (1)
operator new
operator new []
(2)
operator delete
operator delete []
(3)
operator co_await (4) (C++20부터)
op - 다음 연산자 중 하나: + - * / % ^ & | ~ ! = < > + = - = * = / = % = ^ = & = | = << >> >>= <<= == ! = <= >= <=> (C++20부터) && || ++ -- , - > * - > ( ) [ ]
1) 오버로드된 구두점 연산자.
4) co_await 표현식 에서 사용하기 위한 오버로드된 co_await 연산자.

구두점이 아닌 연산자들의 동작은 각각의 페이지에서 설명됩니다. 달리 명시되지 않는 한, 이 페이지의 나머지 설명은 이러한 함수들에 적용되지 않습니다.

설명

연산자가 표현식 에 나타날 때, 그리고 피연산자 중 적어도 하나가 클래스 타입 이거나 열거형 타입 인 경우, 오버로드 해결 을 사용하여 다음 조건과 시그니처가 일치하는 모든 함수 중에서 호출할 사용자 정의 함수를 결정합니다:

표현식 멤버 함수로 사용 시 비멤버 함수로 사용 시 예시
@a (a).operator@ ( ) operator@ (a) ! std:: cin 호출 std:: cin . operator ! ( )
a@b (a).operator@ (b) operator@ (a, b) std:: cout << 42 호출 std:: cout . operator << ( 42 )
a=b (a).operator= (b) 비멤버로 사용 불가 주어진 std:: string s ; , s = "abc" ; 호출 s. operator = ( "abc" )
a(b...) (a).operator()(b...) 비멤버로 사용 불가 주어진 std:: random_device r ; , auto n = r ( ) ; 호출 r. operator ( ) ( )
a[b...] (a).operator[](b...) 비멤버로 사용 불가 주어진 std:: map < int , int > m ; , m [ 1 ] = 2 ; 호출 m. operator [ ] ( 1 )
a-> (a).operator->( ) 비멤버로 사용 불가 주어진 std:: unique_ptr < S > p ; , p - > bar ( ) 호출 p. operator - > ( )
a@ (a).operator@ (0) operator@ (a, 0) 주어진 std:: vector < int > :: iterator i ; , i ++ 호출 i. operator ++ ( 0 )

이 표에서 @ 는 모든 일치하는 연산자를 나타내는 자리 표시자입니다: @a의 모든 전위 연산자, a@의 ->를 제외한 모든 후위 연산자, a@b의 =를 제외한 모든 중위 연산자.

또한 비교 연산자 == , ! = , < , > , <= , >= , <=> 에 대해, 오버로드 해결은 또한 재작성된 후보 operator == 또는 operator <=> 도 고려합니다.

(C++20부터)

오버로드된 연산자들(그러나 내장 연산자들은 제외)은 함수 표기법을 사용하여 호출할 수 있습니다:

std::string str = "Hello, ";
str.operator+=("world");                      // str += "world";와 동일
operator<<(operator<<(std::cout, str), '\n'); // std::cout << str << '\n';와 동일
                                              // (C++17부터) 시퀀싱을 제외하고

정적 오버로드된 연산자

멤버 함수인 오버로드된 연산자는 static 으로 선언될 수 있습니다. 그러나 이는 operator ( ) operator [ ] 에 대해서만 허용됩니다.

이러한 연산자는 함수 표기법을 사용하여 호출할 수 있습니다. 그러나 이러한 연산자가 표현식에 나타날 때는 여전히 클래스 타입의 객체가 필요합니다.

struct SwapThem
{
    template<typename T>
    static void operator()(T& lhs, T& rhs) 
    {
        std::ranges::swap(lhs, rhs);
    }
    template<typename T>
    static void operator[](T& lhs, T& rhs)
    {
        std::ranges::swap(lhs, rhs);
    } 
};
inline constexpr SwapThem swap_them{};
void foo()
{
    int a = 1, b = 2;
    swap_them(a, b); // OK
    swap_them[a, b]; // OK
    SwapThem{}(a, b); // OK
    SwapThem{}[a, b]; // OK
    SwapThem::operator()(a, b); // OK
    SwapThem::operator[](a, b); // OK
    SwapThem(a, b); // error, invalid construction
    SwapThem[a, b]; // error
}
(C++23부터)

제한 사항

  • 연산자 함수는 반드시 클래스, 클래스에 대한 참조, 열거형, 또는 열거형에 대한 참조 타입인 함수 매개변수나 암시적 객체 매개변수를 하나 이상 가져야 합니다.
  • :: (범위 지정), . (멤버 접근), .* (멤버 포인터를 통한 멤버 접근), ?: (삼항 조건) 연산자는 오버로드할 수 없습니다.
  • ** , <> , &| 와 같은 새로운 연산자를 생성할 수 없습니다.
  • 연산자의 우선순위, 그룹화, 또는 피연산자 개수를 변경할 수 없습니다.
  • -> 연산자 오버로드는 반드시 원시 포인터를 반환하거나, -> 연산자가 다시 오버로드된 객체(참조 또는 값으로)를 반환해야 합니다.
  • && || 연산자 오버로드는 단락 평가(short-circuit evaluation)를 상실합니다.
  • && , || , and , 연산자들은 오버로딩될 경우 특별한 시퀀싱 속성 을 잃어버리며, 함수 호출 표기법 없이 사용되더라도 일반 함수 호출처럼 동작합니다.
(C++17 이전)

표준 구현

위의 제한 사항 외에도, 언어는 오버로드된 연산자가 수행하는 작업이나 반환 타입에 대해 다른 제약을 두지 않지만(이는 오버로드 해결에 참여하지 않음), 일반적으로 오버로드된 연산자는 내장 연산자와 최대한 유사하게 동작할 것으로 기대됩니다: operator + 는 인수를 곱하는 대신 더할 것으로 기대되며, operator = 는 할당 등을 수행할 것으로 기대됩니다. 관련 연산자들도 유사하게 동작할 것으로 기대됩니다( operator + operator + = 는 동일한 덧셈 연산을 수행함). 반환 타입은 연산자가 사용될 것으로 예상되는 표현식에 의해 제한됩니다: 예를 들어, 할당 연산자는 참조로 반환하여 a = b = c = d 와 같은 작성이 가능하도록 합니다. 왜냐하면 내장 연산자들이 이를 허용하기 때문입니다.

일반적으로 오버로드되는 연산자들은 다음과 같은 전형적이고 표준적인 형태를 가집니다: [1]

대입 연산자

대입 연산자 operator = 는 특별한 속성을 가집니다: 자세한 내용은 복사 대입 이동 대입 을 참조하십시오.

표준 복사 할당 연산자는 자기 할당에 안전해야 하며 , 좌변을 참조로 반환해야 합니다:

// 복사 대입
T& operator=(const T& other)
{
    // 자기 대입 방지
    if (this == &other)
        return *this;
    // *this가 힙에 할당된 버퍼 mArray와 같은 재사용 가능한 리소스를 관리한다고 가정
    if (size != other.size)           // *this의 리소스를 재사용할 수 없음
    {
        temp = new int[other.size];   // 리소스 할당, 예외 발생 시 아무 작업도 수행하지 않음
        delete[] mArray;              // *this의 리소스 해제
        mArray = temp;
        size = other.size;
    }
    std::copy(other.mArray, other.mArray + other.size, mArray);
    return *this;
}

표준 이동 대입 연산자는 이동된 객체를 유효한 상태로 남겨둘 것 (즉, 클래스 불변식을 유지한 상태)을 기대하며, 아무 작업도 수행하지 않거나 최소한 자기 대입 시 객체를 유효한 상태로 남기고, 비상수 참조로 좌변을 반환하며, noexcept여야 합니다:

// move assignment
T& operator=(T&& other) noexcept
{
    // Guard self assignment
    if (this == &other)
        return *this; // delete[]/size=0 would also be ok
    delete[] mArray;                               // release resource in *this
    mArray = std::exchange(other.mArray, nullptr); // leave other in valid state
    size = std::exchange(other.size, 0);
    return *this;
}
(since C++11)

리소스 재사용의 이점을 얻을 수 없는 복사 할당 상황에서(힙 할당 배열을 관리하지 않거나, std::vector std::string 과 같이 (전이적으로) 해당 기능을 가진 멤버가 없는 경우), 널리 사용되는 편리한 단축 표현이 있습니다: 복사-후-교환 할당 연산자로, 매개변수를 값으로 받아들여(따라서 인자의 값 범주에 따라 복사 및 이동 할당 모두로 작동), 매개변수와 교환한 후 소멸자가 정리하도록 합니다.

// 복사 할당 (copy-and-swap 관용구)
T& T::operator=(T other) noexcept // 복사 또는 이동 생성자를 호출하여 other를 생성
{
    std::swap(size, other.size); // *this와 other 간의 리소스 교환
    std::swap(mArray, other.mArray);
    return *this;
} // other의 소멸자가 호출되어 이전에 *this가 관리하던 리소스 해제

이 폼은 자동으로 strong exception guarantee 를 제공하지만, 자원 재사용은 금지합니다.

스트림 추출 및 삽입

operator>> operator<< 의 오버로드 중 std:: istream & 또는 std:: ostream & 를 좌측 인자로 취하는 것들은 삽입(insertion) 및 추출(extraction) 연산자로 알려져 있습니다. 이들은 사용자 정의 타입을 우측 인자( b in a @ b )로 취하기 때문에 비멤버 함수로 구현되어야 합니다.

std::ostream& operator<<(std::ostream& os, const T& obj)
{
    // 객체를 스트림에 기록
    return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
    // 스트림에서 객체 읽기
    if (/* T를 생성할 수 없는 경우 */)
        is.setstate(std::ios::failbit);
    return is;
}

이러한 연산자는 때때로 friend functions 로 구현됩니다.

함수 호출 연산자

사용자 정의 클래스가 함수 호출 연산자 operator ( ) 를 오버로드하면, 이는 FunctionObject 타입이 됩니다.

이러한 타입의 객체는 함수 호출 표현식에서 사용될 수 있습니다:

// 이 타입의 객체는 일변수 선형 함수 a * x + b를 나타냅니다.
struct Linear
{
    double a, b;
    double operator()(double x) const
    {
        return a * x + b;
    }
};
int main()
{
    Linear f{2, 1};  // 함수 2x + 1을 나타냅니다.
    Linear g{-1, 0}; // 함수 -x를 나타냅니다.
    // f와 g는 함수처럼 사용할 수 있는 객체입니다.
    double f_0 = f(0);
    double f_1 = f(1);
    double g_0 = g(0);
}

많은 표준 라이브러리 알고리즘 들은 동작을 사용자 정의하기 위해 FunctionObject s 를 받아들입니다. operator ( ) 의 특별히 주목할 만한 표준 형식은 없지만, 사용법을 설명하기 위해:

#include <algorithm>
#include <iostream>
#include <vector>
struct Sum
{
    int sum = 0;
    void operator()(int n) { sum += n; }
};
int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5};
    Sum s = std::for_each(v.begin(), v.end(), Sum());
    std::cout << "The sum is " << s.sum << '\n';
}

출력:

The sum is 15

증가 및 감소

후위 증가 또는 감소 연산자가 표현식에 나타날 때, 해당 사용자 정의 함수( operator ++ 또는 operator -- )가 정수 인수 0 를 사용하여 호출됩니다. 일반적으로 이는 T operator ++ ( int ) 또는 T operator -- ( int ) 로 선언되며, 이 인수는 무시됩니다. 후위 증가 및 감소 연산자는 일반적으로 전위 버전을 사용하여 구현됩니다:

struct X
{
    // 전위 증가 연산자
    X& operator++()
    {
        // 실제 증가 연산이 여기서 수행됨
        return *this; // 새로운 값을 참조로 반환
    }
    // 후위 증가 연산자
    X operator++(int)
    {
        X old = *this; // 이전 값 복사
        operator++();  // 전위 증가 연산 호출
        return old;    // 이전 값 반환
    }
    // 전위 감소 연산자
    X& operator--()
    {
        // 실제 감소 연산이 여기서 수행됨
        return *this; // 새로운 값을 참조로 반환
    }
    // 후위 감소 연산자
    X operator--(int)
    {
        X old = *this; // 이전 값 복사
        operator--();  // 전위 감소 연산 호출
        return old;    // 이전 값 반환
    }
};

접두사 증가 및 감소 연산자의 표준 구현은 참조로 반환하지만, 다른 연산자 오버로드와 마찬가지로 반환 타입은 사용자 정의입니다. 예를 들어 std::atomic 에 대한 이러한 연산자의 오버로드는 값으로 반환합니다.

이항 산술 연산자

이항 연산자는 일반적으로 대칭성을 유지하기 위해 비멤버 함수로 구현됩니다(예를 들어, 복소수와 정수를 더할 때, operator + 가 복소수 타입의 멤버 함수라면 complex + integer 만 컴파일되고 integer + complex 는 컴파일되지 않습니다). 모든 이항 산술 연산자에 대해 해당 복합 할당 연산자가 존재하므로, 이항 연산자의 표준 형식은 해당 복합 할당 연산자를 사용하여 구현됩니다:

class X
{
public:
    X& operator+=(const X& rhs) // 복합 할당 (멤버일 필요는 없지만,
    {                           // private 멤버를 수정하기 위해 종종 멤버로 선언됨)
        /* rhs를 *this에 더하는 작업이 여기서 수행됨 */
        return *this; // 참조로 결과 반환
    }
    // 클래스 내부에서 정의된 friend 함수는 인라인이며 비-ADL 조회에서 숨겨짐
    friend X operator+(X lhs,        // lhs를 값으로 전달하면 a+b+c 같은 연쇄 연산 최적화에 도움
                       const X& rhs) // 그렇지 않으면 두 매개변수 모두 const 참조일 수 있음
    {
        lhs += rhs; // 복합 할당 재사용
        return lhs; // 값으로 결과 반환 (이동 생성자 사용)
    }
};

비교 연산자

표준 라이브러리 알고리즘인 std::sort 와 컨테이너인 std::set 은 사용자 정의 타입에 대해 기본적으로 operator < 가 정의되어 있을 것을 기대하며, 이 연산자가 엄격한 약순서(strict weak ordering)를 구현할 것을 요구합니다(따라서 Compare 요구 사항을 충족해야 합니다). 구조체에 대한 엄격한 약순서를 구현하는 관용적인 방법은 std::tie 가 제공하는 사전식 비교(lexicographical comparison)를 사용하는 것입니다:

struct Record
{
    std::string name;
    unsigned int floor;
    double weight;
    friend bool operator<(const Record& l, const Record& r)
    {
        return std::tie(l.name, l.floor, l.weight)
             < std::tie(r.name, r.floor, r.weight); // 동일한 순서 유지
    }
};

일반적으로 operator < 가 제공되면, 다른 관계 연산자들은 operator < 를 기반으로 구현됩니다.

inline bool operator< (const X& lhs, const X& rhs) { /* 실제 비교 수행 */ }
inline bool operator> (const X& lhs, const X& rhs) { return rhs < lhs; }
inline bool operator<=(const X& lhs, const X& rhs) { return !(lhs > rhs); }
inline bool operator>=(const X& lhs, const X& rhs) { return !(lhs < rhs); }

마찬가지로, 부등식 연산자는 일반적으로 operator == 를 기반으로 구현됩니다:

inline bool operator==(const X& lhs, const X& rhs) { /* 실제 비교 수행 */ }
inline bool operator!=(const X& lhs, const X& rhs) { return !(lhs == rhs); }

삼방 비교(예: std::memcmp 또는 std::string::compare )가 제공될 때, 여섯 가지의 양방 비교 연산자 모두 이를 통해 표현될 수 있습니다:

inline bool operator==(const X& lhs, const X& rhs) { return cmp(lhs,rhs) == 0; }
inline bool operator!=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) != 0; }
inline bool operator< (const X& lhs, const X& rhs) { return cmp(lhs,rhs) <  0; }
inline bool operator> (const X& lhs, const X& rhs) { return cmp(lhs,rhs) >  0; }
inline bool operator<=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) <= 0; }
inline bool operator>=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) >= 0; }

배열 첨자 연산자

배열과 같은 읽기 및 쓰기 접근을 제공하는 사용자 정의 클래스는 일반적으로 const 및 비 const 변형인 operator [ ] 에 대해 두 개의 오버로드를 정의합니다:

struct T
{
          value_t& operator[](std::size_t idx)       { return mVector[idx]; }
    const value_t& operator[](std::size_t idx) const { return mVector[idx]; }
};

또는 다음과 같이 명시적 객체 매개변수 를 사용하는 단일 멤버 함수 템플릿으로 표현할 수 있습니다:

struct T
{
    decltype(auto) operator[](this auto& self, std::size_t idx) 
    { 
        return self.mVector[idx]; 
    }
};
(C++23부터)

값 타입이 스칼라 타입으로 알려진 경우, const 변형은 값으로 반환해야 합니다.

컨테이너 요소에 대한 직접 접근이 바람직하지 않거나 불가능한 경우, 또는 lvalue c [ i ] = v ; 와 rvalue v = c [ i ] ; 사용을 구분해야 하는 경우, operator [ ] 는 프록시를 반환할 수 있습니다. 예를 들어 std::bitset::operator[] 를 참조하십시오.

operator [ ] 는 하나의 첨자만 받을 수 있습니다. 다차원 배열 접근 의미론을 제공하기 위해, 예를 들어 3차원 배열 접근 a [ i ] [ j ] [ k ] = x ; 을 구현하려면, operator [ ] 가 2차원 평면에 대한 참조를 반환해야 하며, 이는 자체적으로 operator [ ] 를 가져 1차원 행에 대한 참조를 반환해야 하고, 다시 이 행은 요소에 대한 참조를 반환하는 operator [ ] 를 가져야 합니다. 이러한 복잡성을 피하기 위해 일부 라이브러리는 operator ( ) 를 오버로딩하여 3차원 접근 표현식이 Fortran 스타일의 구문 a ( i, j, k ) = x ; 을 갖도록 합니다.

(C++23 이전)

operator [ ] 는 임의의 개수의 첨자를 받을 수 있습니다. 예를 들어, 3차원 배열 클래스의 operator [ ] T & operator [ ] ( std:: size_t x, std:: size_t y, std:: size_t z ) ; 로 선언된 경우 요소에 직접 접근할 수 있습니다.

#include <array>
#include <cassert>
#include <iostream>
template<typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d
{
    std::array<T, X * Y * Z> m{};
    constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
    {
        assert(x < X and y < Y and z < Z);
        return m[z * Y * X + y * X + x];
    }
};
int main()
{
    Array3d<int, 4, 3, 2> v;
    v[3, 2, 1] = 42;
    std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

출력:

v[3, 2, 1] = 42
(C++23 이후)

비트 연산자

BitmaskType 요구 사항을 구현하는 사용자 정의 클래스 및 열거형은 비트 연산자 operator & , operator | , operator ^ , operator~ , operator & = , operator | = , 그리고 operator ^ = 를 오버로드해야 하며, 선택적으로 시프트 연산자 operator << operator >> , operator >>= , 그리고 operator <<= 를 오버로드할 수 있습니다. 표준 구현은 일반적으로 위에서 설명한 이항 산술 연산자 패턴을 따릅니다.

불리언 부정 연산자

operator ! 연산자는 일반적으로 불린 컨텍스트에서 사용될 사용자 정의 클래스들에 의해 오버로드됩니다. 이러한 클래스들은 또한 불린 타입으로의 사용자 정의 변환 함수를 제공하며(표준 라이브러리 예제는 std::basic_ios 참조), operator ! 의 기대되는 동작은 operator bool 의 반대 값을 반환하는 것입니다.

(C++11 이전)

내장 연산자 ! bool 으로의 상황별 변환 을 수행하기 때문에, 불린 컨텍스트에서 사용될 사용자 정의 클래스들은 operator bool 만 제공하면 되며 operator ! 을 오버로드할 필요가 없습니다.

(C++11 이후)

거의 오버로드되지 않는 연산자

다음 연산자는 거의 오버로드되지 않습니다:

  • 주소 연산자, operator & . 단항 &가 불완전한 타입의 lvalue에 적용되고 완전한 타입이 오버로드된 operator & 를 선언하는 경우, 연산자가 내장 의미를 가지는지 또는 연산자 함수가 호출되는지는 명시되지 않습니다. 이 연산자는 오버로드될 수 있으므로, 일반화 라이브러리들은 사용자 정의 타입 객체의 주소를 얻기 위해 std::addressof 를 사용합니다. 가장 잘 알려진 표준적인 오버로드된 operator & 의 예는 Microsoft 클래스 CComPtrBase 입니다. EDSL에서 이 연산자의 사용 예는 boost.spirit 에서 찾을 수 있습니다.
  • 불리언 논리 연산자, operator && operator || . 내장 버전과 달리, 오버로드된 버전은 단락 평가(short-circuit evaluation)를 구현할 수 없습니다. 또한 내장 버전과 달리, 왼쪽 피연산자를 오른쪽 피연산자보다 먼저 시퀀싱하지 않습니다. (C++17까지) 표준 라이브러리에서 이러한 연산자는 std::valarray 에 대해서만 오버로드됩니다.
  • 쉼표 연산자, operator, . 내장 버전과 달리, 오버로드된 버전은 왼쪽 피연산자를 오른쪽 피연산자보다 먼저 시퀀싱하지 않습니다. (C++17까지) 이 연산자는 오버로드될 수 있으므로, 일반화 라이브러리들은 사용자 정의 타입의 표현식 실행을 시퀀싱하기 위해 a, void ( ) , b 와 같은 표현식을 a, b 대신 사용합니다. boost 라이브러리는 operator, boost.assign , boost.spirit 및 다른 라이브러리에서 사용합니다. 데이터베이스 접근 라이브러리 SOCI 또한 operator, 를 오버로드합니다.
  • 멤버 포인터를 통한 멤버 접근 operator - > * . 이 연산자를 오버로드하는 것에 특별한 단점은 없지만, 실제로는 거의 사용되지 않습니다. 이것이 스마트 포인터 인터페이스 의 일부가 될 수 있다고 제안되었으며, 실제로 boost.phoenix 의 액터들이 그 용도로 사용합니다. cpp.react 와 같은 EDSL에서 더 일반적으로 사용됩니다.

참고 사항

기능 테스트 매크로 표준 기능
__cpp_static_call_operator 202207L (C++23) static operator ( )
__cpp_multidimensional_subscript 202211L (C++23) static operator [ ]

키워드

operator

예제

#include <iostream>
class Fraction
{
    // or C++17's std::gcd
    constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
    int n, d;
public:
    constexpr Fraction(int n, int d = 1) : n(n / gcd(n, d)), d(d / gcd(n, d)) {}
    constexpr int num() const { return n; }
    constexpr int den() const { return d; }
    constexpr Fraction& operator*=(const Fraction& rhs)
    {
        int new_n = n * rhs.n / gcd(n * rhs.n, d * rhs.d);
        d = d * rhs.d / gcd(n * rhs.n, d * rhs.d);
        n = new_n;
        return *this;
    }
};
std::ostream& operator<<(std::ostream& out, const Fraction& f)
{
   return out << f.num() << '/' << f.den();
}
constexpr bool operator==(const Fraction& lhs, const Fraction& rhs)
{
    return lhs.num() == rhs.num() && lhs.den() == rhs.den();
}
constexpr bool operator!=(const Fraction& lhs, const Fraction& rhs)
{
    return !(lhs == rhs);
}
constexpr Fraction operator*(Fraction lhs, const Fraction& rhs)
{
    return lhs *= rhs;
}
int main()
{
    constexpr Fraction f1{3, 8}, f2{1, 2}, f3{10, 2};
    std::cout << f1 << " * " << f2 << " = " << f1 * f2 << '\n'
              << f2 << " * " << f3 << " = " << f2 * f3 << '\n'
              <<  2 << " * " << f1 << " = " <<  2 * f1 << '\n';
    static_assert(f3 == f2 * 10);
}

출력:

3/8 * 1/2 = 3/16
1/2 * 5/1 = 5/2
2 * 3/8 = 3/4

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 1481 C++98 비멤버 접두 증가 연산자는 클래스 타입, 열거형 타입,
또는 해당 타입에 대한 참조 타입의 매개변수만 가질 수 있었음
타입 제한 없음
CWG 2931 C++23 명시적 객체 멤버 연산자 함수는 클래스 타입, 열거형 타입,
또는 해당 타입에 대한 참조 타입의 매개변수를 가질 수 없었음
금지됨

참고 항목

일반 연산자
assignment increment
decrement
arithmetic logical comparison member
access
other

a = b
a + = b
a - = b
a * = b
a / = b
a % = b
a & = b
a | = b
a ^ = b
a <<= b
a >>= b

++ a
-- a
a ++
a --

+ a
- a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

! a
a && b
a || b

a == b
a ! = b
a < b
a > b
a <= b
a >= b
a <=> b

a [ ... ]
* a
& a
a - > b
a. b
a - > * b
a. * b

함수 호출

a ( ... )
콤마

a, b
조건부 연산자

a ? b : c
특수 연산자

static_cast 관련된 타입 간 변환을 수행
dynamic_cast 상속 계층 구조 내에서 변환을 수행
const_cast cv -한정자를 추가하거나 제거
reinterpret_cast 관련 없는 타입 간 변환을 수행
C-style cast static_cast , const_cast , 그리고 reinterpret_cast 의 혼합으로 타입 변환을 수행
new 동적 저장 기간을 가진 객체를 생성
delete new 표현식으로 생성된 객체를 소멸시키고 획득한 메모리 영역을 해제
sizeof 타입의 크기를 조회
sizeof... pack 의 크기를 조회 (C++11부터)
typeid 타입의 타입 정보를 조회
noexcept 표현식이 예외를 발생시킬 수 있는지 확인 (C++11부터)
alignof 타입의 정렬 요구사항을 조회 (C++11부터)

외부 링크

  1. 연산자 오버로딩 StackOverflow C++ FAQ에서