C++ Logo

std-proposals

Advanced search

Re: Default arguments for operators

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Wed, 12 Feb 2020 09:29:20 -0500
On Tue, Feb 11, 2020 at 5:59 PM Joseph Malle via Std-Proposals <
std-proposals_at_[hidden]> 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:

// https://godbolt.org/z/FCf6jn
#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

Received on 2020-02-12 08:32:11