(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