Raven
Raven

Reputation: 3526

std::is_const & Co with value-type wrappers as std::reference_wrapper

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

Answers (1)

Jonathan S.
Jonathan S.

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

Related Questions