Std::Remove_Const with Const References

std::remove_const with const references

std::remove_const removes top level const-qualifications. In const T&, which is equivalent to T const&, the qualification is not top-level: in fact, it does not apply to the reference itself (that would be meaningless, because references are immutable by definition), but to the referenced type.

Table 52 in Paragraph 20.9.7.1 of the C++11 Standard specifies, regarding std::remove_const:

The member typedef type shall name the same type as T except that
any top-level const-qualifier has been removed.
[ Example: remove_const<const volatile int>::type evaluates to
volatile int, whereas remove_const<const int*>::type evaluates
to const int*. — end example ]

In order to strip const away, you first have to apply std::remove_reference, then apply std::remove_const, and then (if desired) apply std::add_lvalue_reference (or whatever is appropriate in your case).

NOTE: As Xeo mentions in the comment, you may consider using an alias template such as Unqualified to perform the first two steps, i.e. strip away the reference, then strip away the const- (and volatile-) qualification.

Why doesn't std::remove_const remove const qualifier?

Note that *first is an lvalue expression, then the result type of decltype(*first) would be const int&, i.e. a reference to const int. The reference is not const itself (it can't be const-qualified, there's no such thing like int& const), using std::remove_const on it will yield the same type, i.e. const int&.

See decltype specifier:


  1. If the argument is any other expression of type T, and

b) if the value category of expression is lvalue, then decltype yields
T&;

You could use std::remove_const with std::remove_reference together:

std::remove_const<std::remove_reference<deref>::type>::type // -> int
^^^^^ // -> const int &
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // -> const int

BTW:

Note that I use std::thread just to get readable types in the errors:

Note that it doesn't give the correct type for this case. Here's a class template helper for this from the Effective Modern C++ (Scott Meyers):

template<typename T>
class TD;

and use it as

TD<deref> td;

You'll get the error message containing the type of deref, e.g. from clang:

prog.cc:16:11: error: implicit instantiation of undefined template 'TD<const int &>'
TD<deref> td;
^

Why does using std::remove_reference and std::remove_const in different order produce different results?

remove_const will only remove a top-level const qualifier, if one exists. In const std::string&, the const is not top-level, hence applying remove_const has no effect on it.

When you reverse the order and apply remove_reference first, the resulting type is const string; now the const is top-level and the subsequent application of remove_const will remove the const qualifier.

Get base type of a template type (remove const/reference/etc.)

I would probaby define a type alias such as:

template<typename T>
using base_type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

Note that, in an article no longer available, R. Martinho Fernandes proposed the name Unqualified for such a type alias.

The standard type trait std::decay, on the other hand does the same as the above and something more for array and function types, which may or may not be what you want.

How to remove const ref modifiers for each element in typename... T

The essential problem is pack expansion. Also, you can use std::decay(c++11) or std::decay_t (c++14) from type_traits for simplicity. The following code should compile with c++14.

#include <tuple>
#include <type_traits>

// new variadic version
namespace variadic
{
template < typename F, typename... Args >
void exec(F* pObj, void(F::*pFct)(Args... args))
{
std::tuple<std::decay_t<Args>...> tN;

// some code that fills tN

// some helper template that 'extracts' the tuple and calls (pObj->*pFct)(ExtractedArgs...)
}
}

struct Test
{
void foo(int i) {}
void bar(const float& f, const int& i) {}
void buu(const float& f, int i) {}
};

int main(int argc, char* argv[])
{
Test t;

variadic::exec(&t, &Test::foo); // ok
variadic::exec(&t, &Test::bar); // ok
variadic::exec(&t, &Test::buu); // ok

return 0;
}

Remove reference with const references

template<class T> struct remove_all { typedef T type; };
template<class T> struct remove_all<T*> : remove_all<T> {};
template<class T> struct remove_all<T&> : remove_all<T> {};
template<class T> struct remove_all<T&&> : remove_all<T> {};
template<class T> struct remove_all<T const> : remove_all<T> {};
template<class T> struct remove_all<T volatile> : remove_all<T> {};
template<class T> struct remove_all<T const volatile> : remove_all<T> {};
//template<class T> struct remove_all<T[]> : remove_all<T> {};
//template<class T, int n> struct remove_all<T[n]> : remove_all<T> {};

I originally also stripped extents (arrays), but Johannes noticed that this causes ambiguities for const char[], and the question doesn't mention them. If we also want to strip arrays (see also ideas mentioned in the comments), the following doesn't complicate things too much:

#include <type_traits>
template<class U, class T = typename std::remove_cv<U>::type>
struct remove_all { typedef T type; };
template<class U, class T> struct remove_all<U,T*> : remove_all<T> {};
template<class U, class T> struct remove_all<U,T&> : remove_all<T> {};
template<class U, class T> struct remove_all<U,T&&> : remove_all<T> {};
template<class U, class T> struct remove_all<U,T[]> : remove_all<T> {};
template<class U, class T, int n> struct remove_all<U,T[n]> : remove_all<T> {};

or with a helper class but a single template parameter:

#include <type_traits>
template<class T> struct remove_all_impl { typedef T type; };
template<class T> using remove_all =
remove_all_impl<typename std::remove_cv<T>::type>;
template<class T> struct remove_all_impl<T*> : remove_all<T> {};
template<class T> struct remove_all_impl<T&> : remove_all<T> {};
template<class T> struct remove_all_impl<T&&> : remove_all<T> {};
template<class T> struct remove_all_impl<T[]> : remove_all<T> {};
template<class T, int n> struct remove_all_impl<T[n]> : remove_all<T> {};

It is normal if all the variants start looking about the same ;-)

std::remove_reference_tstd::remove_cv_tT does the order matter?

There are cases when these two type traits produce different results. For example, let's consider T = const int&.

  1. std::remove_cv_t will remove top-level cv-qualifier, turning const int& into const int&, because there is no top-level cv-qualifier. std::remove_reference_t will then return const int.

  2. In the second case, std::remove_reference_t will return const int, and std::remove_cv_t will transform it into int.

Simple demo

clang-tidy suggest I remove const references, why?

The rationale given here is

With move semantics added to the language and the standard library updated with move constructors added for many types it is now interesting to take an argument directly by value, instead of by const-reference, and then copy. This check allows the compiler to take care of choosing the best way to construct the copy.

Additionally

The transformation is usually beneficial when the calling code passes an rvalue and assumes the move construction is a cheap operation.

However, the documentation states that the only replacement is in the following specific case:

Replaces the uses of const-references constructor parameters that are copied into class fields. The parameter is then moved with std::move().

It doesn't even apply the transformation if the constructor parameter is used more than once.

So I don't think all your functions should have been transformed like that.



Related Topics



Leave a reply



Submit