C++ Logo

std-proposals

Advanced search

Re: proposal: new const-able feature ( use "const?" , or new "constable" keyword) for method/function

From: jianping z <zjpwork_at_[hidden]>
Date: Sat, 18 Jan 2020 09:15:17 -0700
(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
>


Received on 2020-01-18 17:22:30