Conditional const qualifier

780 views
Skip to first unread message

sai...@gmail.com

unread,
Aug 25, 2014, 6:48:35 AM8/25/14
to std-pr...@isocpp.org
Hi,

I would like to float an idea which I have been thinking on since I learned the details of noexcept. I believe turning the const qualifier into a compile time conditional as with noexcept ( defaulting to true ) could finally allow us to fold a lot of code duplication for geters in particular. Optionally support an overload for when the argument isn't bool, in which case the constness would be deduced from whether that argument is const or not ( would perhaps complicate matters, but would simplify client code further ).
As stated, the greatest win as I see it would be to reduce code duplication for geters using the following technique:

struct foo
{
// If const could deduce true / false from the constness of an argument this would obviously get a lot less akward
auto& get_bar() const( std::is_const< typename std::remove_pointer< decltype( this ) >::type >::value )
{
return m_Bar;
}
private:
bar m_Bar;
};

begin() / end() for iterators which decides constness from parameter could be written as ( if constness could be deduced from parameter ):

auto begin() const( *this ) -> iterator < value_type const( *this ) >
{
//...
}


Expressions such as:

std::conditional< /*boolean expression*/, const foo, foo >::type

Instead fold to:

const( /*boolean expression*/ ) foo

enable_if shenanigans when constness is the controlling variable can instead also use this feature ( although that's probably rather rare anyway ). Since the default behavior is compatible with previous versions of the standard I don't think it would break compatibility.

Kind regards,
Sebastian Karlsson

Dietmar Kuehl

unread,
Aug 25, 2014, 8:47:31 AM8/25/14
to std-pr...@isocpp.org
> On 25 Aug 2014, at 12:48, sai...@gmail.com wrote:
> I would like to float an idea which I have been thinking on since I learned the details of noexcept. I believe turning the const qualifier into a compile time conditional as with noexcept ( defaulting to true ) could finally allow us to fold a lot of code duplication for geters in particular.

I quite like the idea of const optinally becoming a compile-time conditional similar to noexcept! In the past I have discussed with people the idea of "constness templates" using something like the snippet below (first in the LWG at the Berlin meeting where the feedback was roughly along the lines "now he has lost it completely"; since then I managed to get better feedback, e.g., at a BSI meeting earlier this year):

template <const cv>
auto& foo::get_bar() cv { return this->m_bar; }

One stumbling block why I haven't pursued that direction is that somewhat requires quite a bit machinary to compute the qualifiers. This need is neatly avoided by making const (and, of course, volatile) conditional on a constant expression. The notation using constness templates is somewhat nicer which could lead to something like

template <bool is_const>
auto& foo::get_bar() const(is_const) { return this->m_bar; }

if the argument of the const can be deduced: unlike the property of whether a function throws an exception, the constness of the object pointed to by this is a known property of an argument. Making this property deducible doesn't look like too much of a stretch.

> As stated, the greatest win as I see it would be to reduce code duplication for geters using the following technique:
>
> struct foo
> {
> // If const could deduce true / false from the constness of an argument this would obviously get a lot less akward
> auto& get_bar() const( std::is_const< typename std::remove_pointer< decltype( this ) >::type >::value )

Of course, even if the argument can't be deduced, the declaration could be simplified to

auto& get_bar() const(is_const<decltype(*this)>)

Using something like:

template <typename T>
constexpr bool is_const = std::is_const<typename std::remove_reference<T>::type>::value;

Since the constness property of the object is likely to be used in multiple places, I'd think it would be desirable to deduce the constness and give it a name in this process. I can also imagine that compiler implementers are not too keen on evaluating arbitrary expressions in the argument of const while determining an overload set (on the other hand, noexcept already requires this anyway).

> begin() / end() for iterators which decides constness from parameter could be written as ( if constness could be deduced from parameter ):
>
> auto begin() const( *this ) -> iterator < value_type const( *this ) >

When following the lead of noexept the qualifier and the expression would be separate, though:

auto begin() const(const(*this)) -> iterator<value_type const(const(*this))>

Since constness of a type can already be determine without much effort, e.g., using variable templates I'm not sure if the a const-testing operator is really needed. This is different for the conditional const qualifier which cannot be emulated for member functions.

One thing worth considering is whether the optional expression could lead to ambiguities. There is no issue for the const/volatile qualifiers of member functions. When conditional const/volatile qualifiers are used for arguments in a function declaration, I could image interaction with existing rules.

In summary:
1. I'm definitely in favor of a mechanism making const and volatile qualifiers optionally conditional on a boolean constexr.
2. A const-testing operator isn't really essential but it would probably be prudent to include in an initial proposal, ideally in a disentangled way making it easy to remove it if necessary.
3. It may be reasonable to provide supporting library components.

Andrew Tomazos

unread,
Aug 25, 2014, 9:22:21 AM8/25/14
to std-pr...@isocpp.org
This seems like an interesting idea, and I encourage you to work on it and submit a proposal.

One thing I would observe is that perhaps you are not attacking the surrounding issue in its full generality.

const in the suffix of a function appertains to the implicit object parameter (this) of a member function.

Notice that:

(a) const is only one kind of specifier; and (b) the implicit object parameter is only one kind of declared entity.

It would seem equally reasonable for any kind of on/off specifiers to be calculated from a boolean constant expression.

For example, let b1, b2, ..., bn be a boolean constant expressions:

    constexpr_if(b1) auto x = y;

    thread_local_if(b2) auto z = w;

    struct D : virtual_if(b3) B { ... };

    void f(const_if(b4) volatile_if(b5) x);

    struct S : T
    {
         void f() override_if(b6) final_if(b7);
    }

and so on.


--

---
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-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Thiago Macieira

unread,
Aug 25, 2014, 10:22:19 AM8/25/14
to std-pr...@isocpp.org
On Monday 25 August 2014 03:48:35 sai...@gmail.com wrote:
> auto& get_bar() const( std::is_const< typename std::remove_pointer<
> decltype( this ) >::type >::value )

This uses the fact that this points to a const object or not to determine
whether this should point to a const object or not. That's a chicken-and-the-
egg problem.

If we do this, "this" cannot appear in the expansion.

Can you give other, useful examples using this functionality without "this" ?
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Matthew Fioravante

unread,
Aug 25, 2014, 1:06:39 PM8/25/14
to std-pr...@isocpp.org
Another thing that could maybe use the same treatment is constexpr. This could be useful for templates where you want certain overloads to be constexpr.

For example the functions in my alignment proposal

template <typename T>
constexpr(std::is_integral<T>::value) T align_down(T x, size_t a) noexcept(std::is_integral<T>::value);

Johannes Schaub

unread,
Aug 25, 2014, 1:13:11 PM8/25/14
to std-pr...@isocpp.org
2014-08-25 15:22 GMT+02:00 Andrew Tomazos <andrew...@gmail.com>:
> This seems like an interesting idea, and I encourage you to work on it and
> submit a proposal.
>
> One thing I would observe is that perhaps you are not attacking the
> surrounding issue in its full generality.
>
> const in the suffix of a function appertains to the implicit object
> parameter (this) of a member function.
>
> Notice that:
>
> (a) const is only one kind of specifier; and (b) the implicit object
> parameter is only one kind of declared entity.
>
> It would seem equally reasonable for any kind of on/off specifiers to be
> calculated from a boolean constant expression.
>
> For example, let b1, b2, ..., bn be a boolean constant expressions:
>
> constexpr_if(b1) auto x = y;
>
> thread_local_if(b2) auto z = w;
>
> struct D : virtual_if(b3) B { ... };
>
> void f(const_if(b4) volatile_if(b5) x);
>
> struct S : T
> {
> void f() override_if(b6) final_if(b7);
> }
>
> and so on.
>
>

What if we abuse static_if for this purpose. If you omit the braces,
it applies to the following qualifier

static_if(b1)
constexpr
auto x = y;

Ville Voutilainen

unread,
Aug 25, 2014, 1:25:34 PM8/25/14
to std-pr...@isocpp.org
On 25 August 2014 20:13, Johannes Schaub <schaub....@googlemail.com> wrote:
> What if we abuse static_if for this purpose. If you omit the braces,
> it applies to the following qualifier
>
> static_if(b1)
> constexpr
> auto x = y;


I do not expect static if to have any chance of survival outside block scopes.

Richard Smith

unread,
Aug 25, 2014, 1:43:46 PM8/25/14
to std-pr...@isocpp.org
I don't see any value in this; just mark the template as 'constexpr'. If a particular specialization doesn't satisfy the relevant requirements, that's fine (you simply can't use that specialization in a constant expression).

Andrew Tomazos

unread,
Aug 25, 2014, 1:58:23 PM8/25/14
to std-pr...@isocpp.org
I thought I just implicitly reserved static_if to mean whether or not the static specifier is applied:

    constexpr bool b = ???;

    struct C
    {
        static_if(b) int x;
    }

if b is true, C::x has static storage duration.  If b is false it is a non-static data member.

:)

Richard Smith

unread,
Aug 25, 2014, 1:59:45 PM8/25/14
to std-pr...@isocpp.org
On Mon, Aug 25, 2014 at 10:13 AM, Johannes Schaub <schaub....@googlemail.com> wrote:
2014-08-25 15:22 GMT+02:00 Andrew Tomazos <andrew...@gmail.com>:
> This seems like an interesting idea, and I encourage you to work on it and
> submit a proposal.
>
> One thing I would observe is that perhaps you are not attacking the
> surrounding issue in its full generality.
>
> const in the suffix of a function appertains to the implicit object
> parameter (this) of a member function.
>
> Notice that:
>
> (a) const is only one kind of specifier; and (b) the implicit object
> parameter is only one kind of declared entity.
>
> It would seem equally reasonable for any kind of on/off specifiers to be
> calculated from a boolean constant expression.
>
> For example, let b1, b2, ..., bn be a boolean constant expressions:
>
>     constexpr_if(b1) auto x = y;
>
>     thread_local_if(b2) auto z = w;
>
>     struct D : virtual_if(b3) B { ... };
>
>     void f(const_if(b4) volatile_if(b5) x);
>
>     struct S : T
>     {
>          void f() override_if(b6) final_if(b7);
>     }
>
> and so on.

I think you're trying to generalize this to a level that is not justified by the problems being solved. constexpr_if and thread_local_if might make sense in some limited cases (on static data members of class templates or variable templates) but in those cases you can get the same effect through template specialization. That might be a little awkward, but these are not common cases.

virtual_if, override_if, and final_if seem extremely odd to me -- when a class (template) overrides a virtual function, that is almost always an invariant part of the design intent and it makes little sense for it to depend on a constant expression. Again, there may be exceptions, but without use cases I don't see a compelling argument to solve this problem.

const_if and volatile_if might have some value, but they're easily expressed in the existing language as

  template<bool, typename T> struct const_if_impl { typedef T type; };
  template<typename T> struct const_if_impl<true, T> { typedef const T type; };
  template<bool B, typename T> using const_if = typename const_if_impl<B, T>::type;

... so don't rise to the level of needing core language support.


That said, I do think that the qualifiers-on-*this problem is worth addressing. The problem seems to me to be that we can't deduce a template parameter from the type of *this, because we don't have an explicit *this parameter. If we did, we could write:

  template<typename T, typename U> using apply_cv_ref = /* apply cv- and ref-qualifiers from T to U */;

  struct S {
    template<typename T>
    apply_cv_ref<T, int &&> f(T &&star_this, int n);
  };

  int g(int &&);
  int h(int &);

  int x = g(S().f(0)); // ok, f returns int &&
  S s;
  int y = h(s.f(0)); // ok, f returns int &

As it is, we only have an "almost"-parameter tucked onto the end of the function type. My strawman suggestion is to use a template-template parameter to capture that parameter's type:

  struct S {
    template<template<typename> class Quals>
    Quals<int> f(int n) Quals;

    template<template<typename> class Quals>
    Quals<int> transparent_access() Quals { return static_cast<Quals<int>>(x); }

  private:
    int x;
  };

Here, Quals will be deduced as a (built-in) alias template that applies the cv- and ref-qualifiers of *this to its type argument.

Thoughts?

Andrew Tomazos

unread,
Aug 25, 2014, 2:38:07 PM8/25/14
to std-pr...@isocpp.org
Why don't we make the following two syntaxes synonymous:

    struct S { void f() const volatile &&; }

    struct S { void f(const volatile S&& this); }

that way we could do:

    struct S { template<typename T> apply_cv_ref<T, int&&> f(T&& this, int n); }

like you want.  Or did I miss the point?

Thiago Macieira

unread,
Aug 25, 2014, 2:45:19 PM8/25/14
to std-pr...@isocpp.org
The point is that the function should be allowed for that condition, the same
way that noexcept does.

sai...@gmail.com

unread,
Aug 25, 2014, 3:02:11 PM8/25/14
to std-pr...@isocpp.org
Tried to send this response earlier, god knows where that ended up but didn't seem to hit the mailing list, anyway, thank you for the suggestions!

>Of course, even if the argument can't be deduced, the declaration could be simplified to
>    auto& get_bar() const(is_const<decltype(*this)>)

I would have assumed as much as well, but for some reason when I tried it out inside a const member function with MSVC2013 it didn't seem to work.

>Using something like:
>
>    template <typename T>
>    constexpr bool is_const = std::is_const<typename std::remove_reference<T>::type>::value;

Ah, that's definitively a better approach, and would probably make const() as an operator redundant.

As both Dietmar & Andrew suggests it might be appropriate for more cv qualifiers to sport this functionality if it's ever accepted.

> This uses the fact that this points to a const object or not to determine
> whether this should point to a const object or not. That's a chicken-and-the-
> egg problem.

Doh, didn't really think about that. It would still seem well defined though unless I'm missing something. When evaluating the const() member function specifier the constness of this would be whatever the object of the calling expression is, after const() is evaluated as member function specifier we'll know if we can discard it from the set or not, and that is whatever constness this gets when we get to the body of the function. At least from a conceptual point of view I think it would work. I'm not that familiar with the details how member functions are matched against invocations however so I might very well be wrong. 

As a consequence however leading or trailing return type could have different constness of this ( and the same goes for the parameter list, that is if we change it, and we're still a valid match ). But since I can't seem to get a leading return with an expression depending on this to compile I'm wondering if it might be that the standard doesn't support it. I don't know if it's standard but both clang & GCC ( but not MSVC 2013 ) seems to compile expressions using this in the function signature as long as it's an trailing return type, which I guess kind of make sense since that happens after the constness of the functions has been decided. If using this would be supported for both leading & trailing return type a side effect of this change would probably mean that deducing the return type the leading way would be using the constness of the object, while the trailing would be whatever we changed it to. That's something which could both probably be seen as a feature or a confusing detail.

Otherwise I'm afraid I can't think of another way to look at it for const qualifying member functions conditionally.

Kind regards,
Sebastian Karlsson

Richard Smith

unread,
Aug 25, 2014, 3:48:41 PM8/25/14
to std-pr...@isocpp.org
On Mon, Aug 25, 2014 at 11:45 AM, Thiago Macieira <thi...@macieira.org> wrote:
On Monday 25 August 2014 10:43:45 Richard Smith wrote:
> On Mon, Aug 25, 2014 at 10:06 AM, Matthew Fioravante <fmatth...@gmail.com
> > wrote:
> >
> > Another thing that could maybe use the same treatment is constexpr. This
> > could be useful for templates where you want certain overloads to be
> > constexpr.
> >
> > For example the functions in my alignment proposal
> >
> > template <typename T>
> > constexpr(std::is_integral<T>::value) T align_down(T x, size_t a)
> > noexcept(std::is_integral<T>::value);
>
> I don't see any value in this; just mark the template as 'constexpr'. If a
> particular specialization doesn't satisfy the relevant requirements, that's
> fine (you simply can't use that specialization in a constant expression).

The point is that the function should be allowed for that condition, the same
way that noexcept does.

That's what you get today if you use 'constexpr'. What am I missing? 

Richard Smith

unread,
Aug 25, 2014, 3:51:10 PM8/25/14
to std-pr...@isocpp.org
I don't actually want that =) I was merely observing why the *this qualifiers are a special case. I also think the result you get here is harder to use than my strawman proposal.

Also, wouldn't your approach make 'this' be a reference rather than a pointer? =)

Richard Smith

unread,
Aug 25, 2014, 3:53:31 PM8/25/14
to std-pr...@isocpp.org
On Mon, Aug 25, 2014 at 12:02 PM, <sai...@gmail.com> wrote:
As a consequence however leading or trailing return type could have different constness of this ( and the same goes for the parameter list, that is if we change it, and we're still a valid match ). But since I can't seem to get a leading return with an expression depending on this to compile I'm wondering if it might be that the standard doesn't support it.

The standard doesn't allow you to use 'this' lexically before the cv-qualifiers in the function type, since that's the point at which its type becomes known.

[expr.prim.general]p3:

"If a declaration declares a member function or member function template of a class X, the expression this
is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifer-seq and the end of the
function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq
and it shall not appear within the declaration of a static member function (although its type and value
category are defined within a static member function as they are within a non-static member function)."

Johannes Schaub

unread,
Aug 25, 2014, 4:15:52 PM8/25/14
to std-pr...@isocpp.org
+1. I would propose that you can leave off the "template<....>" header
aswell. If you don't declare it, it becomes a "dual entity" that
behaves like an injected class name.

auto transparent_access() Quals -> Quals<int> {
Quals &&t = static_cast<Quals&&>(*this);
// ...
}

So if followed by a template argument list or passed to a template
template parameter, it is taken as a template-name. Otherwise, it is a
type-id that stands for the type (using decltype rules for
expressions) of the implied object argument.

Andrew Tomazos

unread,
Aug 25, 2014, 4:39:25 PM8/25/14
to std-pr...@isocpp.org
On Mon, Aug 25, 2014 at 9:51 PM, Richard Smith <ric...@metafoo.co.uk> wrote:
Why don't we make the following two syntaxes synonymous:

    struct S { void f() const volatile &&; }

    struct S { void f(const volatile S&& this); }

that way we could do:

    struct S { template<typename T> apply_cv_ref<T, int&&> f(T&& this, int n); }

like you want.  Or did I miss the point?

I don't actually want that =) I was merely observing why the *this qualifiers are a special case. I also think the result you get here is harder to use than my strawman proposal.

In order to use something, it must first be understood.  I think my version is easier to understand than Quals, but lets not rush to judgement on either.
 
Also, wouldn't your approach make 'this' be a reference rather than a pointer? =)

No, conceptually speaking it has a silent * prefix in the parameter declaration.  The primary expression `this` remains a pointer as usual.

sai...@gmail.com

unread,
Aug 25, 2014, 5:24:38 PM8/25/14
to std-pr...@isocpp.org

[expr.prim.general]p3:

"If a declaration declares a member function or member function template of a class X, the expression this
is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifer-seq and the end of the
function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq
and it shall not appear within the declaration of a static member function (although its type and value
category are defined within a static member function as they are within a non-static member function)."

 
Ah, thanks. Wouldn't that wording permit this being used in the cv qualifier for the member function though? As it's not technically before but within it hehe :)

Personally I'm not a big fan of adding more template parameters for controlling the cv qualifier of the function mostly because I believe it's not as clean & easy to read as if const would behave as noexcept. Another advantage / disadvantage would be that it would probably be unnatural to not support such use of the cv qualifiers in all their contexts. Which I think could be a big boon because I think it makes code once again clearer, especially if you have an expression where you want to both select type, select constness / volatile / mutable ( even though they're often mutually exclusive ), you can then instead split it out to "const( /* expression for const */ ) /* expression for deducing type */;", which is arguably easier to break down and comprehend than nesting it all into one expression.

Kind regards,
Sebastian Karlsson

Thiago Macieira

unread,
Aug 25, 2014, 5:53:28 PM8/25/14
to std-pr...@isocpp.org
On Monday 25 August 2014 12:48:40 Richard Smith wrote:
> > > >
> > > > template <typename T>
> > > > constexpr(std::is_integral<T>::value) T align_down(T x, size_t a)
> > > > noexcept(std::is_integral<T>::value);
> > >
> > > I don't see any value in this; just mark the template as 'constexpr'. If
> >
> > a
> >
> > > particular specialization doesn't satisfy the relevant requirements,
> >
> > that's
> >
> > > fine (you simply can't use that specialization in a constant
> > > expression).
> >
> > The point is that the function should be allowed for that condition, the
> > same
> > way that noexcept does.
>
> That's what you get today if you use 'constexpr'. What am I missing?

Come to think of it, nothing. Template constexprs are allowed to not be
constexpr :-)

We're still missing the ability to overload a constexpr function for compile-
time with a non-constexpr version for runtime, though.
Reply all
Reply to author
Forward
0 new messages