On Thu, 8 Dec 2022 at 03:27, Julien Villemure-Fréchette <julien.villemure@hotmail.com> wrote:
> The background is that some people in the team are arguing that we
should forbid std::move on const objects.

That's not exactly right, its not about calling std::move or not:, there's a very important distinction between calling std::move on an expression and move constructing/move assigning from an object.

I do not like the rule, but I need a case to argue against.
 
A precise, more insightful way to express the guideline is:

"If an object is going to be moved from, then it must not be declared const".

Application of this rule is not concerned with the correct uses of std::move itself (there are other guidelines for this). Actually, more often than not, moving from an object should in fact be done without an explicit call to std::move:

1) for returning by value
```
std:: string func()
{
    std::string ret;
    // modify/build ret

    // good: return a locally declared complete object by value
    return ret;

    // bad: return with std::move
    // return std::move(ret);
    // see http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f48-dont-return-stdmovelocal
}
```
2) for passing by value
```
void g(std::unique_ptr<int>&& p);

void h()
{
    g(std::make_unique<int>(0));
}
```

In your example, the return type of the `get` function is critical to understand correctly the application of the rule "If an object is going to be moved from, then do not declare it const"; since `get(std::tuple<Types...> const&&)` returns a const rvalue reference, then it does not move construct/move assign anything, it merely forwards the indexed subobject with the same value category and qualifier as the tuple argument; so the rule is not applicable in this situation.


Also, note that the example you pulled is an  extremely peculiar one, production code almost never return by rvalue ref (even less const rvalue ref); see http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f45-dont-return-a-t .
The special treatment of `get(std::tuple<Types...> const&&)` is only a special case of perfect forwarding for the const rvalue ref arguments (which is not likely to occur in runtime code, but may be important  (and even necessary) for compile time type computations (ie, template metaprogramming)).

My question is exactly when ....

Specifically, if I want to implement a tuple-like object, is it necessary to implement std::get on const rvalue reference?

template <size_t, typename T,
          std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, bool> = true>
decltype(auto) get(T&&);

template <>
decltype(auto) get<0, A&>(A& a)
{
    return (a.m);
}

template <>
decltype(auto) get<0, const A&>(const A& a)
{
    return (a.m);
}

template <>
decltype(auto) get<0, A>(A&& a)
{
    return std::move(a.m);
}

template <>
decltype(auto) get<0, const A>(const A&& a)
{
    return std::move(a.m);
}


The rule I mentioned above would forbid the last specialization. The key question remains: When would it be useful?

Best regards,

Yongwei