Why can overloaded operators not be defined as static members of a class?

Viewed 34579

C++ syntax allows defining overloaded operators either inside the struct/class like:

struct X
{
   void operator+(X);
}

or outside of the struct/class like:

void operator+(X, X);

but not as:

struct X
{
   static void operator+(X, X);
}

Does anybody know reasons for this decision? Why is the third form not allowed? (MSVC gives a syntax error.) Maybe there is some story behind this?

P.S. The presence of the first and second definitions at the same time creates ambiguity:

1>CppTest1.cxx
1>c:\ballerup\misc\cf_html\cpptest1.cxx(39) : error C2593: 'operator +' is ambiguous
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(13): could be 'void B1::operator +(B1 &)'
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(16): or       'void operator +(B1 &,B1 &)'
1>        while trying to match the argument list '(B1, B1)'

I do not understand why this ambiguity is any better than between 1,3 or 2,3.

9 Answers

I am a couple of years late, but I wanted to offer a contrasting view on this topic from the other answers given. Consider the case of designing a traits class for various ABIs supported by an implementation. For instance, consider the case of designing a traits class for simd ABIs:

template<typename T, typename Abi>
struct abi_traits {};

template<>
struct abi_traits<float, sse>
{
    using vector_type = __m128;

    [[nodiscard]] static constexpr auto operator&(auto a, auto b) noexcept
    {
        return _mm_and_ps(a, b);
    }
};

You'd then be able to call operator overloads for each abi:

abi_traits<T, Abi>::operator&(a, b);

This may seem contrived, but consider the alternative where a named function is used as opposed to an operator overload:

abi_traits<T, Abi>::and(a, b);

You might recognize that "and" is a keyword in C++, and this therefore does not compile. You'd have to mangle the name in some way/shape/form to make this work:

abi_traits<T, Abi>::op_and(a, b);

So then from your vector class, you would have to provide the implementation like this:

template<typename T, typename Abi>
struct simd
{
    ...
    constexpr auto operator&(auto b) noexcept
    {
        traits<T, Abi>::op_and(this->value, b);
    }
};

And you certainly could do this, but then you have to maintain and understand the mangled-name syntax imposed by the programmer. It is much more clear in my mind to allow static overloads, as this would better show intent in the code:

template<typename T, typename Abi>
struct simd
{
    ...
    constexpr auto operator&(auto b) noexcept
    {
        traits<T, Abi>::operator&(this->value, b);
    }
};

So... I get why the other answers might be wary of this... but I totally think there is merit to having static operator overloads where possible. I have seen some proposals appear in the past regarding various efforts to add static operator overloads (for instance, this proposal for static operator()). Perhaps this merits a larger discussion or even a proprosal. I certainly have run into many situations where I thought to myself that it would be much clearer to have this feature. Naming is hard, and it's an underrated problem. If I saw a function with the form op_shl(auto a, auto b), I would be less certain what to do with this function than if I saw a function of the form: operator<<(auto a, auto b). And we can make all the arguments we want about writing good comments and following best-practices with software designs, but at the end of the day, there is no ambiguity with the static operator syntax.

First, I don't know exactly why static operators aren't allowed in C++. As a Python programmer I've seen some great examples of API flexibility using @classmethod methods, which are called as Class.method and it appears no one has suffered from the existence of such a thing.

My guess would be that in C++ it is probably a language-design-related stuff, since at least I don't see anything else preventing that from happening.

Well now, even though you cannot do it legally, you can cheat using #define's and some luck) DISCLAIMER! : Maybe you should NOT do this at home, but it's up to you


#include <iostream>

// for demonstration purposes
// no actual array implementation
class Array
{
public:

  Array() { std::cout << "Array() created\n"; }

  Array operator()()
  {
    std::cout << "surprising operator() call\n";
    return Array();
  }

  int operator[] (int elem_count)
  {
    return elem_count;
  }

};

#define Array Array()

int main()
{
  // this is not a static method, right, but it looks like one. 
  // and if you need the static-method syntax that bad, you can make it. 
  auto arr = Array[7]; // implicitly acts as Array()[N]
  auto x = Array(); // delegate all construction calls to Array.operator()
  std::cout << arr;
}


So, I think you may overload some other operators that way and make it look syntactically as if they were static.

Related