Question

I've been switching Template Factory functions to use (and understand) std::forward to support rvalues and move semantics. My usual boilerplate factory functions for template classes have always marked the parameters as const:

#include <iostream>
#include <utility>

template<typename T, typename U>
struct MyPair{
    MyPair(const T& t, const U& u):t(t),u(u){};

    T t;
    U u;
};

template<typename T, typename U>
std::ostream& operator<<(std::ostream& os, const MyPair<T,U>& pair){
    os << "(" << pair.t << ")=>" << pair.u;
    return os;
}

template<typename T, typename U>
MyPair<T,U> MakeMyPair(const T& t, const U& u){
    return MyPair<T,U>(t,u);
}

using namespace std;
int main(int argc, char *argv[]) {    

    auto no_forward = MakeMyPair(num, num);
    std::cout << no_forward << std::endl;

    auto no_forward2 = MakeMyPair(100, false);
    std::cout << no_forward2 << std::endl;
}

Compiles as expected. Initially I converted MakeMyPair to also pass the parameters as const but this won't compile on my Mac using XCode 4.6:

//$ clang --version
//Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
//Target: x86_64-apple-darwin12.2.0
//Thread model: posix


template<typename T, typename U>
MyPair<T,U> MakeMyPair_Forward(const T&& t, const U&& u){
    return MyPair<T,U>(std::forward<const T>(t),std::forward<const U>(u));
}

int main(int argc, char *argv[]) { 
    int num = 37;
    auto anotherPair = MakeMyPair_Forward(num, true); //This won't work

    auto allRvalues = MakeMyPair_Forward(73, false);   //will compile 
    std::cout << allRvalues  << std::endl;
}

No matching function for call to 'MakeMyPair_Forward' Candidate function [with T = int, U = bool] not viable: no known conversion from 'int' to 'const int &&' for 1st argument

This makes sense from http://en.cppreference.com/w/cpp/utility/forward which states const is deduced and I'm passing lvalue.

  • If a call to wrapper() passes an rvalue std::string, then T is deduced to std::string (not std::string&, const std::string&, or std::string&&), and std::forward ensures that an rvalue reference is passed to foo.
  • If a call to wrapper() passes a const lvalue std::string, then T is deduced to const std::string&, and std::forward ensures that a const lvalue reference is passed to foo.
  • If a call to wrapper() passes a non-const lvalue std::string, then T is deduced to std::string&, and std::forward ensures that a non-const lvalue reference is passed to foo.

Removing const works as I want with rvalues and lvalues. Only passing rvalues as types will work with const on MakeMyPair_Forward's parameters.

//This works for rvalues and lvalues
template<typename T, typename U>
MyPair<T,U> MakeMyPair_Forward(T&& t, U&& u){
    return MyPair<T,U>(std::forward<const T>(t),std::forward<const U>(u));
}

So, the question. Does it make any sense to mark an rvalue reference as const when passing as a parameter? It's not like I can change an rvalue, it's just temporary. I was a bit surprised it compiled with the const after working through and fixing my code. Why would you mark an rvalue parameter as const? Would the point be to only provide an API that takes rvalues? If so, wouldn't you use type traits instead to prevent lvalue references? https://stackoverflow.com/a/7863645/620304

Thanks.

Was it helpful?

Solution

So, the question. Does it make any sense to mark an rvalue reference as const when passing as a parameter?

Here is one place this is done in the C++11 standard:

template <class T> reference_wrapper<T> ref(T&) noexcept;
template <class T> reference_wrapper<const T> cref(const T&) noexcept;
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

I.e. A const T&& is used to capture all rvalues, const or not, and toss them to a compile-time error, while allowing lvalues, even const lvalues to bind and work.

Now this could also probably be done with T&& and an enable_if constraint. But if there's one thing that C++ has taught us over the past few decades: Don't burn any bridges in language design. The C++ programmer will often find a clever way to use a language feature that was initially thought useless. And it is in that spirit that const T&& was left as a legal option.

OTHER TIPS

Another possible use case, that was not taken by the language, but could have been, is smart pointers constructor from raw pointer, e.g.:

// this is NOT the actual ctor in the language but could have been
// see explanation in above link
unique_ptr(T* const&& p) : ptr{p} {}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top