Reputation: 3526
This might be a bit of an academic example (in the sense that I don't see it having a real use case as-is), but I have come across this line of thought a couple of times without coming up with a satisfying answer.
For the sake of argument, let's suppose that I have a template function that I want to behave differently depending on whether the passed value is const or not. A very simple example might be
template< typename T > void print_constness(T&& t) {
if constexpr (std::is_const_v< decltype(t) >) {
std::cout << "T is const\n";
} else {
std::cout << "T is NOT const\n";
}
}
If I pass a mutable reference to this function, it will correctly detect it as non-const. If I pass a const reference to it, then it correctly detects it as const (provided I can prevent the function from making a copy, e.g. by deleting the copy constructor).
Conceptually, std::reference_wrapper< T >
is supposed to represent the same type as const T &
. Therefore, one might expect that the result from passing a const T
to that function is the same as passing a std::reference< const T >
.
But this is not the case, since the wrapper itself is not const. However, for practical purposes, it is. Consider e.g. a template function that has to call a const or non-const overload of a function. When passed a std::reference_wrapper< const T >
, it will pass it to the non-const overload and as soon as that tries to access the reference the compiler will complain (rightfully so).
(Note that I deliberately ignored that you can overload on the constness of your argument - my above example shall only serve as an illustration).
My question is: How to detect and in further steps modify constness of value-type wrappers such as std::reference_wrapper
when the standard std::is_const
, std::add_const
and std::remove_const
clearly don't work?
Is there a generic/standard solution to this problem or would it require implementing custom is_const
, ... traits that specialize on the value wrappers that one expects to encounter?
If so: is it perhaps possible for the implementers of such wrappers to specialize the std type traits so they produce the (semantically) expected result? I kinda expect this to be forbidden...
Upvotes: 1
Views: 87
Reputation: 1854
If you have C++20, there's std::unwrap_reference
:
#include <type_traits>
template<typename T>
using remove_reference_and_wrapper_t =
std::remove_reference_t<std::unwrap_reference_t<std::remove_reference_t<T>>>;
template<typename T>
constexpr static bool is_semantically_const_v =
std::is_const_v<remove_reference_and_wrapper_t<T>>;
static_assert(is_semantically_const_v<const int>);
static_assert(is_semantically_const_v<const int&>);
static_assert(is_semantically_const_v<const int&&>);
static_assert(!is_semantically_const_v<int>);
static_assert(!is_semantically_const_v<int&>);
static_assert(!is_semantically_const_v<int&&>);
static_assert(is_semantically_const_v<std::reference_wrapper<const int>>);
static_assert(!is_semantically_const_v<std::reference_wrapper<int>>);
It's a little unwieldy, but it works.
You can then use the type returned by remove_reference_and_wrapper_t
to further manipulate the object; i.e., get a reference to the actual object:
remove_reference_and_wrapper_t<decltype(t)>& underlying = t;
Upvotes: 2