Guidelines to do constexpr operator-overloading?

10,060

I could not find an idiomatic solution for C++11 (although as a workaround, DyP's suggestion seems acceptable to me).

In C++14 however, where constexpr does not imply const (see Annex C.3.1 of the C++14 Standard Draft n3690), you could simply define both operator *= and operator * as constexpr, and define the latter in terms of the former, as usual:

struct Wrap
{
    int value;    

    constexpr Wrap& operator *= (Wrap const& rhs) 
    { value *= rhs.value; return *this; }

    friend constexpr Wrap operator * (Wrap const& lhs, Wrap const& rhs)
    { return Wrap(lhs) *= rhs; }    
};

Here is a live example, where the above program is being compiled with -std=c++1y on Clang - unfortunately, GCC does not seem to implement this rule yet.

Share:
10,060
TemplateRex
Author by

TemplateRex

Hobbyist C++ programmer. c++ gold badge #191 c++11 gold badge #18 templates silver badge #22 c++-faq: When do extra parentheses have an effect, other than on operator precedence? c++-faq: How to implement classic sorting algorithms in modern C++?

Updated on June 07, 2022

Comments

  • TemplateRex
    TemplateRex almost 2 years

    Consider a simple int Wrapper class with overloaded multiplication operator*= and operator*. For "old-style" operator-overloading, one can define operator* in terms of operator*=, and there are even libraries like Boost.Operators and its modern incarnation df.operators by @DanielFrey that reduce the boilerplate for you.

    However, for compile-time computations using the new C++11 constexpr, this convenience disappears. A constexpr operator* cannot call operator*= because the latter modifies its (implicit) left argument. Furthermore, there is no overloading on constexpr, so adding an extra constexpr operator* to the existing operator* results in an overload resolution ambiguity.

    My current approach is:

    #include <iostream>
    
    struct Wrap
    {
        int value;    
    
        Wrap& operator*=(Wrap const& rhs) 
        { value *= rhs.value; return *this; }
    
        // need to comment this function because of overloading ambiguity with the constexpr version
        // friend Wrap operator*(Wrap const& lhs, Wrap const& rhs)
        // { return Wrap { lhs } *= rhs; }    
    
        friend constexpr Wrap operator*(Wrap const& lhs, Wrap const& rhs)
        { return { lhs.value * rhs.value }; }
    };
    
    constexpr Wrap factorial(int n)
    {
        return n? factorial(n - 1) * Wrap { n } : Wrap { 1 };    
    }
    
    // want to be able to statically initialize these arrays
    struct Hold
    {
        static constexpr Wrap Int[] = { factorial(0), factorial(1), factorial(2), factorial(3) };
    };
    
    int main() 
    {
        std::cout << Hold::Int[3].value << "\n"; // 6
        auto w = Wrap { 2 };
        w *= Wrap { 3 };
        std::cout << w.value << "\n"; // 6
    }
    

    Live output here. My problems with this are:

    • duplication of the multiplication logic in both operator*= and operator*, instead of operator* being expressed in terms of operator*=
    • hence, Boost.Operators no longer works to reduce the boilerplate for writing many other arithmetic operators

    Question: is this the recommended C++11 way of having both a run-time operator*= and mixed run-time/compile-time constexpr operator*? Does C++14 change anything here to e.g. reduce the logic duplication?

    UPDATE: The answer by @AndyProwl is accepted as idiomatic but as per suggestion of @DyP, in C++11 one could reduce the logic duplication at the expense of an extra assignment and counter-intuitive style

        // define operator*= in terms of operator*
        Wrap& operator*=(Wrap const& rhs) 
        { *this = *this * rhs; return *this; }