The specification of std::decay in N4296 leaves the following note:
[ Note: This behavior is similar to the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) conversions applied when an lvalue expression is used as an rvalue, but also strips cv-qualifiers from class types in order to more closely model by-value argument passing. — end note ]
It seems to me that ideally std::decay would model by-value argument passing exactly, but for some reason it's not defined that way.
I think it could be defined in terms of template argument deduction in which case the implementation could also be defined to leverage template argument deduction to exactly model by-value argument passing.
template <typename T>
struct decay {
private:
template <typename U>
static U impl(U);
public:
using type = decltype(impl(std::declval<T>()));
};
Questions:
std::decay and by-value argument passing?std::decay designed to not model by-value argument passing exactly?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.
As Barry pointed out, there is a problem with non-copyable objects. To solve the problem we can write something like this:
template<typename t>
struct decay {
template<typename type> static const type& lref() ;
template<typename u> struct _type_c{
using type = u;
friend constexpr bool operator==(_type_c<u>, _type_c<u>) { return true; }
template<typename o> friend constexpr bool operator==(_type_c<u>, _type_c<o>) { return false; }
};
template<typename u> static constexpr auto impl(const u&) {
return _type_c<u>{};
}
template<typename u, auto sz> static constexpr auto impl(const u(&)[sz]) {
return _type_c<u*>{};
}
template<typename r, typename... p> static constexpr auto impl(r(&)(p...)) {
return _type_c<r(*)(p...)>{};
}
static constexpr auto impl() {
if constexpr(_type_c<t>{}==_type_c<void>{}) return _type_c<void>{};
else return impl(lref<t>());
}
using type = typename decltype(impl())::type;
};
#include <utility>
static_assert( std::is_same_v<typename decay<int>::type, int> );
static_assert( std::is_same_v<typename decay<int&>::type, int> );
static_assert( std::is_same_v<typename decay<int&&>::type, int> );
static_assert( std::is_same_v<typename decay<const int&>::type, int> );
static_assert( std::is_same_v<typename decay<int*>::type, int*> );
static_assert( std::is_same_v<typename decay<void>::type, void> );
it works with C++17 (auto return type) and doesn't require any includes.
also decay requires making a pointer from an array reference and a pointer from a function reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With