Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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:

  1. What are the differences between std::decay and by-value argument passing?
  2. Is std::decay designed to not model by-value argument passing exactly?
  3. Would the implementation above model it exactly?
like image 603
mpark Avatar asked Dec 10 '25 21:12

mpark


2 Answers

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.

like image 135
Barry Avatar answered Dec 13 '25 12:12

Barry


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.

like image 24
zerhud Avatar answered Dec 13 '25 12:12

zerhud



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!