What Is Std::Decay and When It Should Be Used

What is std::decay and when it should be used?

<joke>It's obviously used to decay radioactive std::atomic types into non-radioactive ones.</joke>

N2609 is the paper that proposed std::decay. The paper explains:

Simply put, decay<T>::type is the identity type-transformation except
if T is an array type or a reference to a function type. In those
cases the decay<T>::type yields a pointer or a pointer to a function,
respectively.

The motivating example is C++03 std::make_pair:

template <class T1, class T2> 
inline pair<T1,T2> make_pair(T1 x, T2 y)
{
return pair<T1,T2>(x, y);
}

which accepted its parameters by value to make string literals work:

std::pair<std::string, int> p = make_pair("foo", 0);

If it accepted its parameters by reference, then T1 will be deduced as an array type, and then constructing a pair<T1, T2> will be ill-formed.

But obviously this leads to significant inefficiencies. Hence the need for decay, to apply the set of transformations that occurs when pass-by-value occurs, allowing you to get the efficiency of taking the parameters by reference, but still get the type transformations needed for your code to work with string literals, array types, function types and the like:

template <class T1, class T2> 
inline pair< typename decay<T1>::type, typename decay<T2>::type >
make_pair(T1&& x, T2&& y)
{
return pair< typename decay<T1>::type,
typename decay<T2>::type >(std::forward<T1>(x),
std::forward<T2>(y));
}

Note: this is not the actual C++11 make_pair implementation - the C++11 make_pair also unwraps std::reference_wrappers.

why I need to use std::decay in the following case?

The first line of your error messages says (among other things):

instantiation of 'class std::initializer_list<int&>'

So it is trying to create an initializer_list with a reference type which is not allowed.

Looking at your template code you have:

 std::initializer_list<decltype(*vec.begin())>

Now vec.begin() yields an iterator and dereferencing an iterator yields a reference so you can do things like:

*iter = whatever;

So you need to remove the reference part of the type. std::decay_t does that.

Difference between std::decay and std::remove_reference

Removing reference would leave const and volatile. If that is what you want, then it will suffice.

Removing cvref does most of what decay does, but doesn't convert function types and array types to pointers.

decay converts a type in a way that you could reasonably store a copy of it in an array or in a struct, or return it from or pass it to a function.

Meaning of std::forwardstd::decay_tF(f)

,The code you have shared is toxic.

template <typename F>
class FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f)

what this means is that we expect F to be a value type (because inheriting from a reference isn't possible). In addition, we will only accept rvalues -- we will only move from another F.

  : F{std::forward<F>(f)}
{}

this std::forward<F> is pointless; this indicates that the person writing this code thinks they are perfect forwarding: they are not. The only legal types F are value types (not references), and if F is a value type F&& is always an rvalue reference, and thus std::forward<F> is always equivalent to std::move.

There are cases where you want to

template<class X>
struct wrap1 {
X&& x;
wrap1(X&& xin):x(std::forward<X>(xin)){}
};

or even

template<class X>
struct wrap2 {
X x;
wrap2(X&& xin):x(std::forward<X>(xin)){}
};

so the above code is similar to some use cases, but it isn't one of those use cases. (The difference here is that X or X&& is the type of a member, not a base class; base classes cannot be references).

The use for wrap2 is when you want to "lifetime extend" rvalues, and simply take references to lvalues. The use for wrap1 is when you want to continue the perfect forwarding of some expression (wrap1 style objects are generally unsafe to keep around for more than a single line of code; wrap2 are safe so long as they don't outlive any lvalue passed to them).



template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}

Ok more red flags here. inline constexpr is a sign of nonsense; constexpr functions are always inline. There could be some compilers who treat the extra inline as meaning something, so not a guarantee of a problem.

return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};

std::forward is a conditional move. It moves if there is an rvalue or value type passed to it. decay is guaranteed to return a value type. So we just threw out the conditional part of the operation, and made it into a raw move.

return FixPoint<std::decay_t<F>>{std::move(f)};

this is a simpler one that has the same meaning.

At this point you'll probably see the danger:

template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::move(f)};
}

we are unconditionally moving from the makeFixPoint argument. There is nothing about the name makeFixPoint that says "I move from the argument", and it accept forwarding references; so it will silently consume an lvalue and move-from it.

This is very rude.


So a sensible version of the code is:

template <typename F>
class FixPoint : private F
{
public:
template<class U,
class dU = std::decay_t<U>,
std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
>
explicit constexpr FixPoint(U&& u) noexcept
: F{std::forward<U>(u)}
{}

[SNIP]

namespace
{
template <typename F>
constexpr FixPoint<std::decay_t<F>>
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
}
} // namespace

that cleans things up a bit.

In C++11 when should we explicitly use std::decay?

Simply put, decay::type is the identity type-transformation except if T is an array type or a reference to a function type. In those cases the decay::type yields a pointer or a pointer to a function, respectively.

For more detail please see this https://stackoverflow.com/a/25732651/1691223

What are the differences between std::decay and pass-by-value?

std::decay was proposed in N2069, the motivating example was std::make_pair return a pair of decay-ed types, which is very nearly how std::make_pair is implemented in C++11 (there is a slight exception for reference_wrapper). Note how the proposal originally did not remove cv-qualifiers or top-level reference - I assume this is simply an oversight.

As to the reason it simply models by-value argument passing instead of duplicates it, I can only guess that it may be that the latter is too restrictive. Consider:

struct A {
A(const A& ) = delete;
};

using T1 = std::decay<A>::type; // T1 == A
using T2 = your_decay<A>::type; // compile error
// use of deleted function A(const A&)

I cannot speak as to whether or not it was explicitly specified in this way to allow for decay-ing noncopyable types - but it seems better design to allow this to compile.

should std::common_type use std::decay?

should std::common_type use std::decay?

Yes, see Library Working Group Defect #2141.

Short version (long version, see link above):

  • declval<A>() returns a A&&

  • common_type is specified via declval, n3337:

    template <class T, class U>
    struct common_type<T, U> {
    typedef decltype(true ? declval<T>() : declval<U>()) type;
    };
  • common_type<int, int>::type therefore yields int&&, which is unexpected

  • proposed resolution is to add decay

    template <class T, class U>
    struct common_type<T, U> {
    typedef decay_t < decltype(true ? declval<T>() : declval<U>()) > type;
    };
  • common_type<int, int>::type now yields int

Using std::decay with std::forward

You overthink this. You need just this:

template<typename T>
struct test
{
std::string key;
T value; // as a safety this could be replaced by:
// typename std::decay<T>::type value;

template<typename U, typename US>
test(U&& _value, US&& _key)
: value(std::forward<U>(_value))
, key(std::forward<US>(_key))
{}
};

This perfect forwarding will ensure that all constructors you have listed are available.

Looks like you do not understand what std::decay do, or when/how to use it.
Example: decay<std::string> is pointless, since this just represents std::string type, so you should write just std::string you do not have to do any conversion since you have full control over type passed to decay, you know this type doesn't contain reference or const since you have type this explicitly.

std::decay is useful to define a variable/filed of type which you could assign to. It strips references and constness, C-array converts to pointer, and ensures pointer to functions. See doc example.

Could you explain what was your plan to achieve with this default types for template parameters? I can't figure out what was your intention here.

std::decay and removing const qualifiers

std::decay_t would remove the const from the pointer, had it been const, not the type it's pointing at.

That is, a char* const would decay into a char*.



Related Topics



Leave a reply



Submit