I'm using the add_const typetrait in a scenario where it's applied on a forwarding reference type. Everything looked like no constness was added to the type so I made a small example to verify that was the case (PrintType is an incomplete type that will cause a compilation error, forcing the compiler to spit out the name of the template argument in the error message) :
#include <iostream>
#include <type_traits>
#include <complex>
template <class T>
struct PrintType;
template <class T>
void f(T&& arg)
{
PrintType<std::add_const_t<decltype(arg)>> local;
(void)local;
}
int main()
{
std::complex<double> co;
f(co);
}
The error message says :
main.cpp: In instantiation of 'void f(T&&) [with T = std::complex<double>&]':
main.cpp:20:9: required from here
main.cpp:12:48: error: 'PrintType<std::complex<double>&> local' has incomplete type
PrintType<std::add_const_t<decltype(arg)>> local;
ie the trait transformed my type to T = std::complex<double>& instead of T = std::complex<double> const&
The type trait works as expected. You should consider what you are attempting to do, which is to add constness to a reference. You can't rebind a reference (it's not mutable), so essentially any reference is a const reference
T& == T& const
What I suppose you expect to do is to create a reference to a const type (which reminds me of the const iterator vs const_iterator thingy) which cant' be done this way, for the same reason you can't typedef a reference type to a reference to a const type this way :
typedef T& ref_t;
typedef const ref_t const_ref_t; // This is not T const& !!
To sum up, adding const to a reference type makes it a const reference (which is the same as a reference) and not a reference to a const type
For situations like these, it can be useful to have a type trait that transfers the reference to a new type. This can complement another trait that does the same for const and volatile, implemented almost identically. In your case, you should only need to worry about lvalue references if you use T instead of decltype(arg). However, if using a lambda, you definitely do need to worry about rvalue references as well.
Here is a sample implementation:
template<typename T, bool ApplyLvalueRef, bool ApplyRvalueRef>
struct apply_ref {
static_assert(!(ApplyLvalueRef && ApplyRvalueRef), "Improper use: T cannot be & and &&");
using possibly_lref = std::conditional_t<
ApplyLvalueRef,
std::add_lvalue_reference_t<T>,
T
>;
using type = std::conditional_t<
ApplyRvalueRef,
std::add_rvalue_reference_t<possibly_lref>,
possibly_lref
>;
};
template<typename From, typename To>
struct transfer_ref : apply_ref<To, std::is_lvalue_reference<From>{}, std::is_rvalue_reference<From>{}> {};
template<typename From, typename To>
using transfer_ref_t = typename transfer_ref<From, To>::type;
At first glance, it seems a bit silly to have separate booleans for lvalue vs. rvalue. However, this allows for applying neither. There should never be a situation in which both are true, and this is enforced by the static assertion.
Now we can easily write the function:
template <class T>
void f(T&& arg)
{
using with_const = std::add_const_t<std::remove_reference_t<T>>;
PrintType<transfer_ref_t<T, with_const>> local;
(void)local;
}
Since we cannot apply const to a reference, we must strip it, add const to the referred type, and then add the reference back.
Related
I have some code but I do not understand what does it do
template <typename T, typename U = T>
struct MyStruct
{
};
template <typename T>
[[nodiscard]] inline T fromValue(const QJsonValue& json)
{
return MyStruct<std::decay_t<T>>::get(json);
}
I do not understand what heppenes in line
return MyStruct<std::decay_t<T>>::get(json);
And Why we use empty struct?
You use std::decay_t in order to obtain the basic type without cv-qualification (plus some fewer used special features for arrays and function pointers). Starting from C++20, for most cases it should be sufficient to use std::remove_cvref_t<T> for this.
The function signature
template <typename T>
[[nodiscard]] inline T fromValue(const QJsonValue& json)
{
return MyStruct<std::decay_t<T>>::get(json);
}
basically tells you, that regardless of the cv-qualification (T, T&, T const&, T volatile const&, etc.) the class template should be instantiated only with the base type T. Thereafter, the static function get is called -- static because those are functions that can be called without an object.
EDIT: sorry, I just recognized that the struct is empty. So your code won't work, as there simply is no get function available.
Note however, that there is a danger of returning a dangling reference here, because for T& as template parameter, you return a reference. Moreover, you don't want consts, volatiles, etc. in your return type. Therefore I would question the design of this function and use it with caution.
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)?
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
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.
I've tried searching for this but every term I think of ends up with totally unrelated results.
I have a function (template) that takes a pointer-to-member as a parameter, but I cannot seem to implicitly treat the member being pointed to as const. Using const_cast works, but I'd like to avoid having to explicitly call it if I can.
struct MyStruct
{
int *_array;
int _size;
};
template<typename C, typename T>
void DoSomething(T* C::* arr, int siz)
{
// do some read-only stuff with the member here
}
template<typename C, typename T>
void ConstDoSomething(T* C::* arr, int siz)
{
DoSomething<C, T const>(arr, siz);
// DoSomething<C, T const>(const_cast<T const* C::*>(arr), siz); // works
}
MyStruct ms;
ConstDoSomething<MyStruct const, int>(&MyStruct::_array, ms._size); // note: cannot convert ‘arr’ (type ‘int* MyStruct::*’) to type ‘const int* MyStruct::*’
This is a simplified example that demonstrates the problem I'm having with a more complex class tree. I'm trying to avoid the cast because it would be required by the calling code (e.g., the person using the class template(s)).
UPDATE: When I first posted this I accidentally used a code sample that didn't generate the same error. It took me a fair amount of testing to determine the root cause, which was that I'm adding the const qualifier in the template arguments. The above sample now properly demonstrates the behavior I'm using.
What compiler do you use? I've tried your code Visual Studio 2012 and 2013 and it was compiled without any warnings or errors.
Either way - you should try const_cast instead of static_cast when you are playing with constness
In lieu of a better solution (for now at least), I'm creating a separate overload to accept a pointer-to-non-const-member and then use const_cast to call the originating methods.
As noted, the sample above was for simplicity and clarity's sake, but in actuality I am using several class templates, each inheriting from the other, and so forth. This has resulted in the following rather ugly solution:
// C = containing class (for pointer-to-members, std::nullptr_t if none)
// T = type of data being referenced
template<typename _C, typename _T> struct MakeMemberPointer // STL doesn't provide this??
{
public:
typedef _T _C::* Type;
};
// Note: base-class template has specialization for C == std::nullptr_t
typedef typename std::conditional<std::is_same<C, decltype(nullptr)>::value,
T const* C::*,
T const*
>::type N;
typedef typename std::conditional<std::is_member_pointer<T>::value,
typename MakeMemberPointer<
C,
typename std::add_pointer<
typename std::remove_const<
typename std::remove_reference<
decltype(*(static_cast<C*>(nullptr)->*static_cast<N>(nullptr))) // T const&
>::type // reference removed -> T const
>::type // const removed -> T
>::type // pointer added -> T*
>::Type, // member pointer -> T* C::*
typename std::add_pointer<typename std::remove_const<typename std::remove_pointer<N>::type>::type>::type
>::type NNoConst;
void DoSomething(N t) noexcept
{
}
void DoSomething(NNoConst t) noexcept
{
DoSomething(const_cast<N>(t));
}
The class containing all of this is a const-only class derived from a non-const base class (but used here with const members due to template arguments). Declaring all this inside the class is highly preferable (to me) over having to use const_cast in the calling code, but I still don't see why gcc isn't allowing this conversion implicitly (after all, it's just adding const qualifiers!!).
Among the many benefits of const qualification is to make an API more understandable, example:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
With the introduction of rvalue references, one can benefit from perfect forwarding but often const qualifiers are removed, example:
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that function2 won’t change its input?
template<typename T> int function2(T&& in);
// can explicitly forward the input if it's an rvalue
Apart from documentation, is there a good way to describe that
function2 won’t change its input?
Yes. Stick with the C++03 solution:
template<typename T> int function1(T const& in);
// clearly, the input won’t change through function1
The benefits of perfect forwarding are that you don't want to assume if something is const or non-const, lvalue or rvalue. If you want to enforce that something is not modified (i.e. that it is const), then explicitly say so by adding const.
You could do this:
template<typename T> int function1(T const&& in);
// clearly, the input won’t change through function1
However everyone who read your code would wonder why you've used rvalue references. And function1 would cease to accept lvalues. Just use const & instead and everyone will understand. It is a simple and well understood idiom.
You don't want to perfectly forward. You want to enforce immutability.
You could say this:
template <typename T>
typename std::enable_if<immutable<T>::value, int>::type
function(T && in)
{
// ...
}
where you have something like:
template <typename T> struct immutable
: std::integral_constant<bool, !std::is_reference<T>::value> {};
template <typename U> struct immutable<U const &>
: std::true_type {};
This way, the template will only be usable if the universal reference is either a const-reference (so T = U const &) or an rvalue-reference (so T is not a reference).
That said, if the argument is not going to be changed, you could just use T const & and be done with it, since there's nothing to be gained from binding mutably to temporary values.
First comes the definition of our new function object, contains_t. It
could have inherited from the helper class std::unary_function (part
of the C++ Standard Library, intended to facilitate the creation of
the correct typedefs) and have the argument and result types defined
automatically, but to make things clear, the required typedefs are
provided explicitly. The argument type has changed from const
boost::any& to boost::any, to avoid a potential
reference-to-reference, which is illegal. The implementation is just
as before, only here it is placed in the function call operator.
template <typename T> struct contains_t {
typedef boost::any argument_type;
typedef bool result_type;
bool operator()(boost::any a) const {
return typeid(T)==a.type();
}
};
Why the following implementation has potential to receive reference-to-reference?
template <typename T> struct contains_t {
typedef boost::any argument_type;
typedef bool result_type;
bool operator()(const boost::any& a) const {
return typeid(T)==a.type();
}
};
Thank you
Since boost::any can store anything, it can store references too. Therefore if you have a reference to a boost::any, you may accidentally end up with a reference to a reference internally.
i.e. boost::any can represent any type T. Let T be a reference of type U, i.e. T = U&. So taking a reference of the type T creates a reference to a reference of type U, which is not allowed in C++03 (C++11 will allow it though).