std::as_const
helper function templateISO/IEC JTC1 SC22 WG21 N4380 - 2015-02-06
ADAM David Alan Martin (adamartin@FreeBSD.org)
(amartin172@bloomberg.net)
Alisdair Meredith (ameredith1@bloomberg.net)
This paper proposes a new helper function template std::as_const
, which would
live in the <utility>
header. A simple example usage:
#include <utility>
#include <type_traits>
void
demoUsage()
{
std::string mutableString= "Hello World!";
const std::string &constView= std::as_const( mutableString );
assert( &constView == mutableString );
assert( &std::as_const( mutableString ) == mutableString );
using WhatTypeIsIt= std::remove_reference_t< decltype( std::as_const( mutableString ) >;
static_assert( std::is_same< std::remove_const_t< WhatTypeIsIt >, std::string >::value,
"WhatTypeIsIt should be some kind of string." );
static_assert( !std::is_same< WhatTypeIsIt, std::string >::value,
"WhatTypeIsIt shouldn't be a mutable string." );
}
The C++ Language distinguishes between 'const Type' and 'Type' in ADL lookup for selecting function overloads. The selection of overloads can occur among functions like:
int processEmployees( std::vector< Employee > &employeeList );
bool processEmployees( const std::vector< Employee > &employeeList );
Oftentimes these functions should have the same behavior, but sometimes free (or
member) functions will return different types, depending upon which qualifier
(const or non-const) applies to the source type. For example,
std::vector< T >::begin
has two overloads, one returning
std::vector< T >::iterator
, and the other returning
std::vector< T >::const_iterator
. For this reason cbegin
was added to the
container member-function manifest.
A larger project often needs to call functions, like processEmployees
,
and selecting among specific const or non-const overloads. Further, within
C++11 and newer contexts, passing an object for binding or perfect forwarding
can often require specifying a const qualifier applies to the actual object.
This can also be useful in specifying that a template be instantiated as
adapting to a "const" reference to an object, instead of a non-const reference.
const_cast< const T & >( object )
const_cast< std::add_const< decltype( object ) >::type & >( object )
const_cast( object )
which is
equivalent to the above(const) object
which is equivalent to
the aboveobject const
, or object const.blah
or object.blah const
as_const
, which this paper proposesThis conversion, or alternative-viewpoint, is always safe. Following the rule that safe and common actions shouldn't be ugly while dangerous and infrequent actions should be hard to write, we can conclude that this addition of const is an operation that should not be ugly and hard. Const-cast syntax is ugly and therefore its usage is inherently eschewed.
Regarding the above alternatives, each can be discussed in turn:
const_cast< const T & >( object )
:The benefits of this form are:
The drawbacks of this form are:
decltype( object )
const_cast
)const_cast< std::add_const< decltype( object ) >::type & >( object )
:The benefits of this form are:
decltype( object )
;
it merely requires that he use that type-deductionThe drawbacks of this form are:
typename
usage, when used
in a template context, due to the add_const
metafunctionconst_cast
)const_cast( object )
:The benefits of this form are:
The drawbacks of this form are:
const_cast
,
over the other _cast
forms currently in the core language(const) object
:The benefits of this form are:
The drawbacks of this form are:
std::as_const( object )
:The benefits of this form are:
decltype( object )
std::move
and std::forward
, in terms of
wrapping commonly needed, but difficult to write cast expressionsThe drawbacks of this form are:
std::constify( object )
std::to_const( object )
std::constant( object )
std::view_const( object )
std::make_const( object )
std::add_const( object )
std::const_view( object )
In proposing std::as_const( object )
, we feel that the name is sufficiently
terse yet descriptive. Every other name had at least one drawback.
In the <utility>
header, the following code should work:
// ...
// -- Assuming that the file has reverted to the global namespace --
namespace std
{
template< typename T >
inline typename std::add_const< T >::type &
as_const( T &t ) noexcept
{
return t;
}
}
// ...
The above implementation only supports safely re-casting an l-value as const
(even if it may have already been const). It is probably desirable to have
xvalues and prvalues also be usable with as_const
, but there are some issues
to consider. Some examples of contexts we'd like to consider for an expanded
proposal are:
std::string getString();
auto &x= as_const( getString() );
auto &x= const_cast< const std::string & >( getString() );
void function( std::string & );
void function( const std::string & );
function( as_const( getString() ) );
An alternative implementation which would support all of the forms used above, would be:
template< typename T >
inline const T &
as_const( const T &t ) noexcept
{
return t;
}
template< typename T >
inline const T
as_const( T &&t ) noexcept( noexcept( T( t ) ) )
{
return t;
}
We believe that such an implementation helps to deal with lifetime extension
issues for temporaries which are captured by as_const
, but we have not fully
examined all of the implications of these forms. We are open to expanding
the scope of this proposal, but we feel that the utility of a simple-to-use
as_const
is sufficient even without the expanded semantics.