On Tue, Feb 11, 2020 at 5:59 PM Joseph Malle via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Currently, operators do not support default arguments in most cases and must have a predetermined number of arguments.

For example, operator/ must have two arguments and those arguments cannot have defaults.  I propose allowing such operators to have additional arguments as long as they have defaults.  The default arguments must come after the regular arguments.

auto operator/(U, V) // OK
auto operator/(U, V, W) // Error, and should remain an error
auto operator/(U, V = V(), W) // Error, and should remain an error

auto operator/(U, V, W = W()) // Error, but should be ok

I don't think operator() would need to change at all as it already supports default arguments.  It would now need wording to allow defaults in any order.  Perhaps there are other operators with special cases that I haven't thought of.

The reason I want to have this feature is to use std::source_location with operators.  As far as I can tell, default arguments are the only sensible way to use std::source_location.

This seems like a reasonable goal, and a reasonable way of getting there. But it needs a lot more fleshing out with the technical details. AIUI, essentially you're proposing to completely overhaul the ridiculous circa-1984 way that C++ does operator overloading, and replace it with something more like expression rewriting plus function overload resolution. So
    z = x / y;
would first be rewritten into
    z = operator/(x, y);  // and/or x.operator/(y)? How to specify that "double" lookup? Some of this work is done for you already; but is all of it?
and then do overload resolution to see which operator/s in scope were viable for that call.

This is a good goal. But you'll have to figure out how to specify it. In particular I can think of these corner cases:
    struct S {
        void operator++(int i=0);  // currently ill-formed
        friend void operator*(S, int i=0);  // currently ill-formed; also operators +, -, &
    };
    S s; ++s;  // can this now call s.operator++(0)?
    *s;  // can this now call operator*(s, 0)?

    void operator+(decltype(nullptr), int, S = {}) { }  // this would be ill-formed for some reason, right?
    nullptr + 2;  // this can't possibly call the user-defined operator+, can it?

 
I think this is a backwards compatible change (but perhaps it could be detected by concepts? not 100% sure).

There's no such thing as a purely backward compatible change, in the presence of SFINAE. So don't worry about that.
Worry more about how to specify the behavior you want.


Btw, for source_location specifically, I believe the state-of-the-art hack (besides "don't use it, keep using your old DEBUG_LOG macros because there's nothing wrong with them, and wait for something better to come along") is to insert an implicitly constructible wrapper type. Like this:

#include <experimental/source_location>
#include <iostream>

struct OldDebugStream {
    OldDebugStream& operator<<(const char *msg) {
        std::cout << msg;
        return *this;
    }
    OldDebugStream& operator<<(int i) {
        std::cout << i;
        return *this;
    }
};
OldDebugStream ods_;
#define ods ods_ << __FILE__ << ":" << __LINE__ << ":"

struct NewDebugStream {
    struct Annotated {
        using SourceLoc = std::experimental::source_location;
        NewDebugStream *s_;
        Annotated(NewDebugStream& s, 
                  SourceLoc loc = SourceLoc::current()) : s_(&s)
        {
            *this << loc.file_name() << ":" << loc.line() << ":";
        }
    };
    friend Annotated operator<<(Annotated a, const char *msg) {
        std::cout << msg;
        return a;
    }
    friend Annotated operator<<(Annotated a, int i) {
        std::cout << i;
        return a;
    }
};
NewDebugStream nds;

int main()
{
    ods << "Hello world! " << 42 << "\n";
    nds << "Hello world! " << 42 << "\n";
}

–Arthur