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
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