What should the semantic of wrapper classes be?

128 views
Skip to first unread message

Vincent Reverdy

unread,
Jul 26, 2016, 2:05:33 AM7/26/16
to ISO C++ Standard - Future Proposals
Hello.

Please find the document attached.
It's not really a proposal... but we need help on this question and people really don't seem to agree.
So... any help, advice, opinion is welcome.

Thanks,
Vincent

wrapping.pdf

Tony V E

unread,
Jul 26, 2016, 6:34:38 PM7/26/16
to Standard Proposals
I would say value<const T> makes no sense.

A value is a value.  All values are const.  17 is const.  It is always 17.
What may be NOT const is a variable holding the value 17.

So value<const T> should not exist.  It should be collapsed to value<T>.  const value<T> is fine.

pointer<const T> makes sense, because it is referring to a value living somewhere else.
But for value<T> I assume the T lives within value<T>.

Alternatively, value<const T> should work the same as const value<T>.

What does auto do?

const int x = 17;
auto y = x; // y is not const?

What does optional<const T> do?

Based on the "essence" of optional, if it worked at all, it should allow you to assign to it once (first assign is construction) and then fail further assignments.
But I'm pretty sure we didn't make it throw an exception in that case.



Tony V E

unread,
Jul 26, 2016, 6:35:59 PM7/26/16
to Standard Proposals
On Tue, Jul 26, 2016 at 2:05 AM, Vincent Reverdy <vinc...@gmail.com> wrote:
By the way, thanks for tackling this.  We really need some of these higher-level papers to help us form guidance going forward.

Tony

FrankHB1989

unread,
Jul 26, 2016, 10:17:49 PM7/26/16
to ISO C++ Standard - Future Proposals
在 2016年7月27日星期三 UTC+8上午6:34:38,Tony V E写道:


On Tue, Jul 26, 2016 at 2:05 AM, Vincent Reverdy <vinc...@gmail.com> wrote:
Hello.

Please find the document attached.
It's not really a proposal... but we need help on this question and people really don't seem to agree.
So... any help, advice, opinion is welcome.


I would say value<const T> makes no sense.

A value is a value.  All values are const.  17 is const.  It is always 17.
What may be NOT const is a variable holding the value 17.

No. Both value of expression of class or array types and value stored in some object can be either const or non-const. Actually the language specifies that a prvalue of int is not cv-qualified.

Note constant expression does not implying constness.

The essential problems are in the core language design, as it does not urge people to differentiate "value of expression" and "stored value of object" clearly enough in most cases. Explicit value category (which is only meaningful to the former) may differentiate the contexts, but often messes it worse, because people often misunderstand or ignore them. Also note in core's terminology, pointers and references are types, but values are fundamentally different. You can't only mimic these words literally, but also need to coin new meaning before simply putting them together with only some examples and mapping rules.

 
So value<const T> should not exist.  It should be collapsed to value<T>.  const value<T> is fine.

You are basically folds "rvalue" to "value", as ISO C does. Things are harder in C++ as it may too easily raise more confusion.

Thiago Macieira

unread,
Jul 26, 2016, 10:24:54 PM7/26/16
to std-pr...@isocpp.org
On terça-feira, 26 de julho de 2016 18:34:36 PDT Tony V E wrote:
> So value<const T> should not exist. It should be collapsed to value<T>.
> const value<T> is fine.
>
> But for value<T> I assume the T lives within value<T>.
>
> Alternatively, value<const T> should work the same as const value<T>.

I still don't understand the point of value<T>. If T stores a value, why do we
need value<T>? It stands to reason that value<T> does something extra, on top
of what T can do. That something extra is quite important and may affect what
a value<const T> might be.

Should I understand it like optional<T>, which can store a T, but it can also
be disengaged and store nothing? optional<const int> makes no sense.

Or maybe like atomic<T>. Here we have that atomic<const T> is the same as
const atomic<T>.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

FrankHB1989

unread,
Jul 26, 2016, 11:49:48 PM7/26/16
to ISO C++ Standard - Future Proposals


在 2016年7月27日星期三 UTC+8上午10:24:54,Thiago Macieira写道:
On terça-feira, 26 de julho de 2016 18:34:36 PDT Tony V E wrote:
> So value<const T> should not exist.  It should be collapsed to value<T>.
> const value<T> is fine.
>
> But for value<T> I assume the T lives within value<T>.
>
> Alternatively, value<const T> should work the same as const value<T>.

I still don't understand the point of value<T>. If T stores a value, why do we
need value<T>? It stands to reason that value<T> does something extra, on top
of what T can do. That something extra is quite important and may affect what
a value<const T> might be.

Should I understand it like optional<T>, which can store a T, but it can also
be disengaged and store nothing? optional<const int> makes no sense.

Or maybe like atomic<T>. Here we have that atomic<const T> is the same as
const atomic<T>.
If you want to assume that T lives within lifetime of value<T>, you perhaps have to also assume T stores a value, as I don't find another obvious way to guarantee the lifetime of T. Then value<T> and value<const T> are essentially different.
Otherwise, if you only want to use value<T> as rvalue of type T, then value<const T> should always decay to value<T>. However, then it will behave more like a "reference".
I'm not sure which kind of "value" is intended by the spelling. It is at least confusing to me.

If both use are needed, just use object type T as usual. Perhaps the only reason to wrap it is that operations on bit-fields are too limited, as they are not capable as operand of builitin& or bound to a reference, etc. Ugly.

FrankHB1989

unread,
Jul 26, 2016, 11:53:20 PM7/26/16
to ISO C++ Standard - Future Proposals


在 2016年7月27日星期三 UTC+8上午11:49:48,FrankHB1989写道:


If you want to assume that T lives within lifetime of value<T>, you perhaps have to also assume T stores a value, as I don't find another obvious way to guarantee the lifetime of T.

Sorry, I meant "value<T> stores a value of object type T".
 

Vincent Reverdy

unread,
Jul 27, 2016, 1:31:39 AM7/27/16
to ISO C++ Standard - Future Proposals
I still don't understand the point of value<T>. If T stores a value, why do we
need value<T>?

In the context of the bit proposal, originally bit_value was not a class template and was implemented as a wrapper around a bool providing the right semantics for a bit (like no weird integral promotion) and some extra functionalities (like set/reset/flip members).

However if bit_value is a wrapper around a bool, while bit_reference references a bit within an unsigned integer (for example the 3rd bit of a value of uint64_t type), it is impossible for a bit_reference to reference a bit within a bit_value: one cannot make implicit conversions from a bit_value& to a bit_reference<uint64_t>. If bit_value is templated, then the value of the bit can be stored in any unsigned integral type, and it becomes possible to implicitly convert a bit_value<uint64_t>& to a bit_reference<uint64_t>. It adds some flexibility to the design that could help the writing of generic algorithms. (If you don't agree, please let me know, the idea of make bit_value a template class was brought to me at the Oulu meeting so it's very recent).

T. C.

unread,
Jul 27, 2016, 6:31:55 PM7/27/16
to ISO C++ Standard - Future Proposals
It would be much easier to understand this argument if you can show a sample algorithm that works better with the implicit conversion.

Matt Calabrese

unread,
Jul 27, 2016, 8:34:48 PM7/27/16
to ISO C++ Standard - Future Proposals
When I'm in generic code and I need to make a member of a dependent type "T", I instead encapsulate an instance of a wrapper type that makes it appropriately Regular in a way that is logically consistent and allows proper encapsulation of cv-qualified objects and reference types. Something similar to what I use is shown below (this is a simplified, untested approximation of what I actually use). I used to not have cv support, sort of similar to what Tony described, though I've discovered that what I present here actually handles cv-qualification and is what is consistent with intent in a logically-consistent manner. Basically what it does with cv qualification is it encapsulates the cv-unqualified version, with the associated functions of a Regular type defined in terms of the cv-unqualified version, but with user-level accessors of the underlying object that only ever give back references to the object with the originally-specified qualifiers attached. This wrapper that I describe is only ever used internal to something like a container implementation or an optional or variant implementation is never leaked back to the user. It is just used behind-the-scenes to give logically consistent semantics.

Before the actual wrapper code, here is an example usage of the value< T > wrapper, assuming that you want to make a std::vector-like type that works with vectors of const-qualified types or of reference types:

///////////////////////////

template< class T >
class vector
{
 public:
  /*...*/

  // If T is const, this returns a reference-to-const even though
  // this operator[] is not const.
  // If T is a reference type, this behaves appropriately
  // with respect to reference-collapsing rules.
  decltype( auto ) operator []( std::size_t i ) /*noexcept*/
  {
    // "access" is shown in the implementation example below.
    return access( data[ i ] );
  }

  // Note that things like assignment, push_back, and erase still work.
 private:
  // value< T > is the wrapper template
  std::unique_ptr< value< T >[] > data;
};

///////////////////////////

Similarly, you can use that to make a std::optional of a cv-qualified or reference type in generic code in a way that makes sense, or you can use it to make a variant of such types, etc. I've found that this kind of support is absolutely essential in generic code when directly or indirectly receiving dependent types from the user,

Here is an example implementation, though again, this is a simplified version:

///////////////////////////

#include <memory>
#include <type_traits>
#include <utility>

template< class T > struct access_impl {};

template< class T > struct identity { using type = T; };

template< class T >
struct value
{
  friend struct access_impl< value >;
  static_assert( std::is_object< T >::value );

  value() = default;
  
  template< class LazyT = std::remove_cv_t< T > >
  constexpr explicit value( LazyT v )
    noexcept( std::is_nothrow_move_constructible< LazyT >::value )
    : v( std::move( v ) ) {}
  
  // NOTE: Comparisons should forward to v.
 private:
  std::remove_cv_t< T > v;
};

template< class T >
struct value< T& >
{
  friend struct access_impl< value >;
  static_assert( std::is_object< T >::value );
  
  constexpr explicit value( T& v ) noexcept
    : v( std::addressof( v ) ) {}
  
  // NOTE: Comparisons should deal with the pointer value.
 private:
  T* v;
};

template< class T >
struct value< T&& >
{
  friend struct access_impl< value >;
  static_assert( std::is_object< T >::value );

  constexpr explicit value( T&& v ) noexcept
    : v( std::addressof( v ) ) {}
  
  // NOTE: One *might* consider the following appropriate (move-only)...
  // value( value&& ) = default;
  // value& operator =( value&& ) = default;

  // NOTE: Comparisons should deal with the pointer value.
 private:
  T* v;
};

// Requires Source is a reference type
template< class Source, class Target >
struct give_qualifiers_to;

#define GIVE_QUALIFIERS_TO_SPEC( qual )          \
template< class Source, class Target >           \
struct give_qualifiers_to< Source qual, Target > \
{                                                \
  using type = Target qual;                      \
}

GIVE_QUALIFIERS_TO_SPEC( & );
GIVE_QUALIFIERS_TO_SPEC( && );
GIVE_QUALIFIERS_TO_SPEC( const& );
GIVE_QUALIFIERS_TO_SPEC( const&& );
GIVE_QUALIFIERS_TO_SPEC( volatile& );
GIVE_QUALIFIERS_TO_SPEC( volatile&& );
GIVE_QUALIFIERS_TO_SPEC( volatile const& );
GIVE_QUALIFIERS_TO_SPEC( volatile const&& );

#undef GIVE_QUALIFIERS_TO_SPEC

template< class Source, class Target >
using give_qualifiers_to_t
  = typename give_qualifiers_to< Source, Target >::type;

template< class T >
struct access_impl< value< T > >
{
  template< class Self >
  static constexpr give_qualifiers_to_t< Self&&, T >
  run( Self&& self ) noexcept
  {
    return std::forward< Self >( self ).v;
  }
};

template< class T >
struct access_impl< value< T& > >
{
  template< class Self >
  static constexpr give_qualifiers_to_t< Self&&, T& > run( Self&& self ) noexcept
  {
    return *self.v;
  }
};

template< class T >
struct access_impl< value< T&& > >
{
  template< class Self >
  static constexpr give_qualifiers_to_t< Self&&, T&& >
  run( Self&& self ) noexcept
  {
    return std::forward< give_qualifiers_to_t< Self&&, T&& > >( *self.v );
  }
};

// Requires: T is a valid instantiation of value
template< class T >
decltype( auto ) access( T&& object ) noexcept
{
  return access_impl< std::remove_cv_t< std::remove_reference_t< T > > >
  ::run( std::forward< T >( object ) );
}

///////////////////////////

I anticipate that this is a controversial view as it is not consistent with what the standard library containers do, nor what things like optional/variant do, though I've found this to actually be a failing of the standard library. I believe that having semantics such as what I describe benefits both generic code and user code as it removes the need for higher-level special casing around reference and cv-qualified object types for both kinds of users (library authors and library users).

- Matt Calabrese

Matt Calabrese

unread,
Jul 28, 2016, 1:07:08 PM7/28/16
to ISO C++ Standard - Future Proposals
On Wed, Jul 27, 2016 at 5:34 PM, Matt Calabrese <cala...@x.team> wrote:
Basically what it does with cv qualification is it encapsulates the cv-unqualified version, with the associated functions of a Regular type defined in terms of the cv-unqualified version, but with user-level accessors of the underlying object that only ever give back references to the object with the originally-specified qualifiers attached.

After looking back at my actual implementation, when dealing with object types I only strip const and not volatile, and in retrospect, I believe that was the correct choice.
Reply all
Reply to author
Forward
0 new messages