c++ 为成员函数生成必要的ref限定重载

rjee0c15  于 2022-12-15  发布在  其他
关注(0)|答案(1)|浏览(154)

I have this class:

template<typename T, size_t N>
class Array {
    private:
        T array[N];

    public:
        template <typename... InitValues>
        constexpr Array(InitValues... init_values) 
            : array{ init_values... } {}

        [[nodiscard]]
        consteval int len() const noexcept { return sizeof(array) / sizeof(T); }
}

I would like to know, for such a simple member function, when I should provide the necessary ref-qualified overloads.
With the actual code, I can compile and run the following code:

constexpr collections::Array a = collections::Array<long, 5>{1L, 2L, 3L};
SECTION("length of the array") {
    REQUIRE( a.len() == 5 );
    REQUIRE( collections::Array<int, 1>{1}.len() == 1 );
}

1- Why I can compile the second REQUIRE that contains the call with the rvalue ?
Now I am gonna change the len() member function to this:

[[nodiscard]]
consteval int len() const& noexcept { return sizeof(array) / sizeof(T); }

2- Why I can compile both with the const& ? I suppose that they are two are different ref-qualified usages. I assume that I can make the call with the first one, which is an lvalue , but can't understand why I can compile the second having defined the len() method as const& .
Last change:

[[nodiscard]]
consteval int len() const&& noexcept { return sizeof(array) / sizeof(T); }

And finally, I got a compiler error on a.get<I>() .

'this' argument to member function 'len' is an lvalue, but function has rvalue ref-qualifier
        REQUIRE( a.len() == 5 );

that works perfect if I comment that line of code and I just run:

REQUIRE( collections::Array<int, 1>{1}.len() == 1 );

and also I could use std::move(a) to perform the cast of a to an rvalue reference and make the code compile. But I don't want to do that.

  • What is the correct way of code those examples in terms of ref-qualified overloads?
  • Don't forget about the questions on the examples above

EDIT:
I will add another member function that could potentially do different things based on the ref-qualified implementation (or that what I am suppose that could happen):

template <size_t I>
requires concepts::AccessInBounds<I, N>
constexpr T get() const noexcept {
    return array[I];
}

template <size_t I>
requires concepts::AccessInBounds<I, N>
constexpr T& get() const& noexcept {
    return array[I];
}
nkhmeac6

nkhmeac61#

To question 1: why not? The rule is the same as for lvalues: you can call const member functions regardless of the constness of the object.
To question 2: Because it is meant to be identical to having a const& function parameter: the function can be called with any lvalue or rvalue. It exists primarily to allow you to distinguish between lvalue and rvalue overloads:

class Array {
    // These two declarations would be ambiguous for Array rvalues
    // int len() const;
    // int len() &&;

    // These are not: your test expressions will use different overloads 
    int len() const&;
    int len() &&;
};

The two functions in your edit are also ambiguous, for both lvalues and rvalues. A motivating example would be more along these lines: suppose my class provides functionality to some resource that could be expensive to copy, but is cheaper to move, say a std::vector.

template<class T>
class VectorView {
    std::vector<T> vector;

public:
    // ...

    constexpr std::vector<T> const& base() const noexcept { return vector; }
};

Now there is no way for a user of this class to transfer ownership of the vector data back from a view object, even if that would be useful when calling the base() function on an rvalue. Because it is in the spirit of C++ to avoid paying for things you do not need, you could allow this by adding an rvalue-qualified overload that instead returns an rvalue reference using std::move.
So the answer to whether you need this kind of overload is it depends, which is unfortunately also in the spirit of C++. If you were implementing something like my example class for the standard library, then you certainly would, because it is based on std::ranges::owning_view . As you can see on that page, it covers all four possible base()s. If you were instead only using a reference to a source range, it would be unexpected and inappropriate to move from that object, so the related ref_view only has a const base() function like the one I wrote.

相关问题