Post by Barry RevzinPost by m***@gmail.comHello, I have written a proposal for Unified Call Syntax, based on the
previous discussion.
https://1drv.ms/u/s!AifghXUnpCc8hAaU7HpR2avsms3b
It seems like this paper is very focused on attempting to solve the
problems with the previous UFCS proposals, rather than trying to solve the
problems that UFCS was trying to solve. As a result, it doesn't actually
Having two call syntaxes makes it hard to write generic code: which syntax
Post by m***@gmail.comcan we assume from a template argument type? When we start writing
concepts, we will either have to support both syntaxes (verbose,
potentially doubling the size of concepts) or make assumptions about how
objects of certain binds of types are to be invoked (and we may be wrong
close to 50% of the time). Today, we already have the problem that many
standard-library types are supported by two functions, such as, begin(x)
and x.begin(), and swap(x,y) and x.swap(y). This is an increasing problem
It is a well-known and long-standing problem that C++ has two incompatible
Post by m***@gmail.comcalling syntaxes:[...] Unfortunately, this means that calling code must
know whether a function is a member function or not. In particular, this
syntactic difference defeats writing generic code which must select a
single syntax and therefore has no reasonable and direct way to invoke a
function f on an object x without first knowing whether f is a member
function or not for that type. Because there is no single syntax that can
invoke both, it is difficult or impossible to write generic code that can
adapt
This note explores the possibility of providing a uniform call syntax by
Post by m***@gmail.comgiving member functions preference over non-member functions. Offering the
choice between the x.f(y) and f(x,y) notations with different meanings
means that different people will chose differently for their function
definitions, so that users have to know the choice and write calls
appropriately. This gives users more opportunities for making mistakes,
makes it harder to write generic code, and has led to replication when
people define both a member and a non-member function to express the same
thing. I suggest that providing different meanings to the two syntaxes
offers no significant advantage
Now, I don't think generic code is the *only* motivation for having this
kind of feature - but it was a big motivation for the original papers.
Basically, being able to, in generic code, write either x.f(y) or f(x, y)
and have that work regardless if X provides a member f or a non-member f.
True, this was the main motivation of the said papers, but this was not the
original motivation.
The original motivation was not about templates, it was about extending
classes one have no access to.
Of course template code made all this *much* more attractive and the
possibility to solve it without an opt in - even more so.
This, however, has failed.
Yet, as the template code can only be truly served by having a default UFC,
the proposal envisions the possibility of eventually switching some default
on, and having a syntax overriding the defaults to match the user needs.
It also envisions migrating the code over a period of time.
The proposal does not exclude UCS as per the original papers.
Meanwhile, the users receive the original feature request!
Post by Barry RevzinWith your proposal - I still can't do this. In order for f(x, y) to find
x.f(y), I would have to already know the X::f exists and bring it in with a
using-declaration. But, if I already know it exists, why wouldn't I just
template <typename T>
void algo(T t) {
using T::f; // what if there is no T::f?
f(t, Y{});
}
We would have to add something about being able to write dependent
using-declarations and having them just do nothing instead of failing
(which is, I think, questionable), but then every function template would
just be littered with all of these using-declarations for every possible
unqualified call? That seems fairly hostile to library writers.
And then, in order for x.f(y) to find f(x, y), I need to redeclare the
type? This definitely won't work in generic code. Where would I do it? What
if it's a nested type or a template specialization or ... ?
No, this is *user side*, library writes write how they please, knowing the
user can *always* enable the opposite syntax.
// lib
template <typename T> //< assume a concept for T to make the interface
official
void algo(T t) {
f(t, Y{});
}
// user
class C { void f(Y) {} }
// User sees the concept for T, or worst cases, sees an error as f is not
found
// using C::f; //< user choice 1
// void f(Y) {} //< user choice 2
algo(C());
Library writers should not care much about UFC!
In future we *might* make looking into the class default, we *might* use
and improve the syntax to alter the behavior in some way.
The point is having a place to communicate the link b/w the free world and
member world.
Post by Barry RevzinAdditionally, I'm not sure this opt-in syntax meaningfully addresses the
concern that I have seen from library authors about allowing x.f(y) to find
f(x, y) - which is that now if class X adds anything named "f" at all, even
if it's a private member that users aren't intended to be aware of, it
Declared this way, if lib::Image ever gets a 'save' method, we will get a
Post by m***@gmail.comredefinition error, instead of silent change of implementation.
There are not many details in the proposal, but private members should not
affect the extended interface.
I know current call rules check access last, but this does not have to be
the case when free functions are imported.
class C {};
void f(C&) {}
class C using
{
using f; //< overload only against the public interface
};
// later
class C { void f(C&) {} };
c.f(); //< still calling the free function, we would have errored out
anyway!
Isn't that always correct? Yeah, the free hides the member, but a private
member, not callable anyway.
The only place where this could create problems is when c.f() is called
from C itself, but C has control over what it declares and what it imports.
Post by Barry RevzinDoesn't really address their concern. User code is *still broken *by a
seemingly irrelevant change. Sure, the error might be better - you could
clearly say that "X::f is a member function but you're trying to say it
doesn't exist" instead of "error: trying to access private member int"
which would be incredibly confusing. And if an X::f was added that was
public and completely compatible with ::f, you would get an error instead
of silently potentially-different code, which is good. But the point
remains that user code is still broken.
There are multiple stages to pass to get the code broken, and each and
every one is a *user choice:*
1. The user imports his functions into a foreign class public interface
2. The user does NOT accept the future implementation of the function by
the class itself
3. The user uses the dot notation
4. The user does *not* use qualified call.
Compared to a default UCS we have TWO new stages of consent!
And BTW the code can be broken similarly today
namespace lib {
template<class T>
void f(T) { std::cout<<__PRETTY_FUNCTION__<<'\n';}
}
// user
void f(int) { std::cout<<__PRETTY_FUNCTION__<<'\n'; }
using lib::f;
f(5);
If lib decides to add f(int) user code breaks.
Post by Barry RevzinSo I'm not sure this is the right direction.
Barry
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/35b2400e-b308-4dc3-b2aa-7631908adc98%40isocpp.org.