Reputation: 1226
Imagine some function (RetrieveResult
), returning an object by pointer/reference/value - I don't know and don't want to know, because things may change. I just want to store the result, using auto
and also protect that object from accidental changing in the current scope or, for example, if the object is propagated upwards.
It is quite intuitive just to write:
const auto result = RetrieveResult();
and everything works fine, if RetrieveResult
returns an object by value or by reference. But if the function returns a pointer, constancy is applied to that pointer, not to the object the poiter points to. What way I still can change the object. Writing
const auto const result = ....
results in the compilation error:
duplicate 'const'
Of course, I can declare variable like this: const auto* ... const auto* const...
But that way ties me close to pointers, i.e. it isn't a universal solution.
Is it possible to preserve true constancy, and, in the same time, provide flexibility (independency of the concrete type)?
Upvotes: 3
Views: 385
Reputation: 21520
There is experimental support for this utility called std::propagate_const
in the Library Fundamentals v2. You can write a type trait on top of that that does this for you (if you do not have std::propagate_const
you can consider writing it yourself :))
namespace {
template <typename T, typename = std::enable_if_t<true>>
PropagateConst {
using type = T;
};
template <typename T>
PropagateConst<T, std::enable_if_t<std::is_same<
decltype(*std::declval<std::decay_t<T>>()),
decltype(*std::declval<std::decay_t<T>>())>::value>> {
using type = std::propagate_const_t<std::decay_t<T>>;
};
template <typename T>
using PropagateConst_t = typename PropagateConst<T>::type;
template <typename Type>
decltype(auto) propagate_const(Type&& in) {
return PropagateConst_t<std::add_rvalue_reference_t<Type>>{in};
}
} // <anonymous>
// then use it like this
const auto result = propagate_const(RetrieveResult());
Note that the solution I have above only checks for the presence of an operator*
in the possible pointer type. You might want to consider writing a more extensive test for that.
Also note that this uses reference collapsing in the propagate_const
example so expect at least a move to happen in cases where you might be expecting elision. You can optimize it based on your use case. I just thought I would outline what was in my head. Maybe that would help
Upvotes: 4
Reputation: 275600
template<class T>
struct very_const_t { using type=T; };
template<class T>
struct very_const_t<T*> { using type=typename very_const_t<T>::type const*; };
template<class T>
struct very_const_t<T&> { using type=typename very_const_t<T>::type const&; };
template<class T>
typename very_const_t<T>::type&&
very_const( T&& t ) { return std::forward<T>(t); }
then:
const auto result = very_const(RetrieveResult());
note that this can block elision. I carefully do not block move semantics, however.
This does not move const
into smart pointers. If you want that:
template<class T, class D>
struct very_const_t<std::unique_ptr<T,D>> { using type=std::unique_ptr<typename very_const_t<T>::type const, D>; };
template<class T>
struct very_const_t<std::shared_ptr<T>> { using type=std::shared_ptr<typename very_const_t<T>::type const>; };
will do it for unique
and shared
.
Upvotes: 1