(revised for some errors)
thank you Bo, that's a very good reminder.
after a second thought, for ref-qualifiers, as currently only
& and && can be paired for member functions, I think
&&? is enough (so we do not need &? and &&??).
also, as we know we have 4 qualifiers "const", "volatile",
"&", "&&", which we usually call them cv-qualifiers
and ref-qualifiers, though "volatile" is not commonly used, I
think the ? qualifier postfix can also be applied to it, so we
have
1) const? -> const, or ()
2) volatile? -> volatile, or ()
3) &&? -> &&, or &
with existing type traits like "is_const", "is_lvalue_reference",
"is_rvalue_reference", "is_volatile" etc, we can make better use
of ? qualifier postfix to write more versatile member functions.
here, let me demonstrate it first with a simple example.
//example-1a (class written without ? qualifier postfix)
class A
{
public:
void f() & { std::cout << "lvalue" <<
std::endl; }
void f() && { std::cout << "rvalue" <<
std::endl; }
void f() const & { std::cout << "const lvalue"
<< std::endl; }
void f() const && { std::cout << "const rvalue"
<< std::endl; }
};
int main()
{
A a1;
a1.f(); // lvalue
std::move(a1).f(); // rvalue
const A a2 = A();
a2.f(); // const lvalue
std::move(a2).f(); // const rvalue
}
by using type traits and ? qualifier postfix, we can write member
function f() only once for same output.
//example-1b (class A rewritten with ? qualifier postfix)
class A
{
public:
void f() const? &&? { std::cout <<
(std::is_const_v<const? int> ? "const " : "") <<
(std::is_lvalue_reference_v<int &&?> ? "lvalue" :
"rvalue") << std::endl; }
}
based on example-1a, the auto generated class A by compiler
//auto generated class for example-1b (before code trim from
compiler)
class A
{
public:
void f() & { std::cout <<
(std::is_const_v<int> ? "const " : "") <<
(std::is_lvalue_reference_v<int &> ? "lvalue" :
"rvalue") << std::endl; }
void f() && { std::cout <<
(std::is_const_v<int> ? "const " : "") <<
(std::is_lvalue_reference_v<int &&> ? "lvalue" :
"rvalue") << std::endl; }
void f() const & { std::cout <<
(std::is_const_v<const int> ? "const " : "") <<
(std::is_lvalue_reference_v<int &> ? "lvalue" :
"rvalue") << std::endl; }
void f() const && { std::cout <<
(std::is_const_v<const int> ? "const " : "") <<
(std::is_lvalue_reference_v<int &&> ? "lvalue" :
"rvalue") << std::endl; }
}
the auto generated class works almost the same as the class A in
example-1a, though it looks verbose for most of us.
we know type traits can be evalutated at compile time, if compiler
support to trim code at compile time for "expr1 ? expr2 : expr3"
to only "expr2" or "expr3" depending on the value of "expr1"
evaluated at compile time, the code will look better and more
similar
//auto generated class for example-1b (after code trim from
compiler)
class A
{
public:
void f() & { std::cout << "" << "lvalue"
<< std::endl; }
void f() && { std::cout << "" <<
"rvalue" << std::endl; }
void f() const & { std::cout << "const " <<
"lvalue" << std::endl; }
void f() const && { std::cout << "const " <<
"rvalue" << std::endl; }
}
as the example demonstrates, if compiler support compile time code
trim, the final generated code could be almost as good as the one
we written in example-1a.
what we wish for compile time code trim
1. for "constexpr1 ? expr2 : expr3", if "constexpr1" can be
evaluated at compile time, compiler can trim it into "expr2" or
"expr3" depending on value of "expr1"
2. for "if(constexpr1) statment2; else statement3;", if "expr1"
can be evaluated at compile time, compiler can trim it to
"statement2;" or "statement3;" depending on the value of
"constexpr1"
for the first example, it may seem that compile time code trim is
not needed as developer won't see the final generated class, only
the compiler will compile the final generated class. but it can
make difference to next example with ref-qualifiers on return
value as mentioned by Barry in the earlier thread.
//example-2a
template <typename T>
class optional {
public:
auto operator*() & -> T& { return value;
} //1
auto operator*() const& -> T const& { return value;
} //2
auto operator*() && -> T&& {
return std::move(value); } //3
auto operator*() const&& -> T const&& {
return std::move(value); } //4
private
T value;
};
we may rewrite it with ? qualifier postfix and type traits
//example-2b (class optional rewritten with ? qualifier postfix)
template <typename T>
class optional {
public:
auto operator*() const? &&? -> T const? &&?
{ return is_lvalue_reference_t<int &&?> ? value :
std::move(value); }
private:
T value;
};
in example-2a, for the last 2 overloaded operator functions (3,
4), if compiler supports to return member value of rvalue object
as rvalue, we can simply write function return as "return value;"
instead of "return std::move(value)".
if we have to manually convert the reference from lvalue to
rvalue, without compile time code trim, compiler may complain the
mismatched return value types of "value" and "std::move(value)". I
use this example as sample though mismatched reference type may
not happen for this member function definition, it could happen
for other member function definitions.
with complie time code trim, the compiler will not complain after
auto generated member functions are trimmed, and the final
generated code will look exactly the same as the class in
example-2a.
currently, it looks "if constexpr(condition)" are supported by
c++17. it's better to have "constexpr(condition) ? expr1 : expr2"
supported in the future, even without it, we can simply use "if
constexpr()" to achieve it.
with all the type traits and "if constexpr()" available, now it is
the right time to bring the ? qualifier postfix support to the
language so we can use it to write simple code for most
cv-qualifiers, and ref-qualifiers overloaded member functions.
Thanks,
Jianping
On 12/18/2019 02:25 AM, Bo Persson via
Std-Proposals wrote:
On
2019-12-17 at 22:20, jianping z via Std-Proposals wrote:
Thank you and Michael Hava for the
updates, it turns out my C++ knowledge needs to be updated to
C++17.
Here are some of my thoughts about your concern,
1. the "&&?" notation
we may treat "&&" as a token having two "&", and
each "?" can optionally remove one "&",
following the rule, "&&??" can be used to optionally
remove two "&" from "&&" with "??".
a) const? -> (), const //2 variations with single "?"
b) &? -> (), & //2 variations with
single "?"
c) &&? -> &, && //2
variations with single "?"
d) &&?? -> (), &, && //3
variations with double "?" ("??")
here, left side is the notations, right side are the variations
of the final tokens to replace the notation, "()" means no token
(notation removed)
by explaining "?" like this, developer may able to accept this
concept easily. though "&?", and "&&??" are hardly
needed, but they may still be usefully, for example
Readability concerns aside, using double question marks gets you
very close to accidentally stumble into trigraphs. Although now
formally removed from C++, several compilers still support them as
an extension.
For example, see "What does the C ??!??! operator do?"
https://stackoverflow.com/questions/7825055/what-does-the-c-operator-do
Bo Persson