Search code examples
c++c++11templatesoverloadingforwarding-reference

Why doesn't forwarding reference work in this case?


#include <vector>

using namespace std;

template<typename T, typename = decltype(&T::size)>
void f1(T)
{}

template<typename T, typename = decltype(&T::size)>
void f2(T&)
{}

template<typename T, typename = decltype(&T::size)>
void f3(T&&)
{}

int main()
{
    vector<int> coll;

    f1(coll); // ok
    f2(coll); // ok
    f3(coll); // error : no matching function for call to 'f3'
}

main.cpp(21,6): note: candidate template ignored: substitution failure [with T = > std::vector<int, std::allocator<int> > &]: type 'std::vector<int, std::allocator<int> > &' cannot be used prior to '::' because it has no members

void f3(T&&)

My compiler is clang 4.0.

To my surprise, f3(coll) fails, while f1(coll) and f2(coll) are both ok.

Why does a forwarding reference not work in this case?


Solution

  • Because T is deduced as a reference type, you need to use std::remove_reference

    template<typename T, typename = decltype(&std::remove_reference_t<T>::size)>
    void f3(T&&)
    {}
    

    Full example:

    #include <vector>
    #include <type_traits>
    
    using namespace std;
    
    template<typename T, typename = decltype(&T::size)>
    void f1(T)
    {}
    
    template<typename T, typename = decltype(&T::size)>
    void f2(T&)
    {}
    
    template<typename T, typename = decltype(&std::remove_reference_t<T>::size)>
    void f3(T&&)
    {}
    
    int main()
    {
        vector<int> coll;
    
        f1(coll); // ok
        f2(coll); // ok
        f3(coll); // ok
    }
    

    Demo


    Generally, when using Forwarding References, type modification utilities comes in very handy; primarily because forwarding references preserves both value category and cv qualifications.

    Example 1:

    • The code below fails to compile because T is deduced as std::vector<int>& and you cannot have a non-const reference bind to a temporary in foo:

      #include <vector>
      
      template<typename T>
      void foo(T&&){
          T nV = {3, 5, 6};
      }
      
      int main(){
          std::vector<int> Vec{1, 2 ,3, 4};
          foo(Vec);
      }
      
    • You can remove the reference to get it to work:

      #include <vector>
      
      template<typename T>
      void foo(T&&){
          using RemovedReferenceT = std::remove_reference_t<T>;
          RemovedReferenceT nV = {3, 5, 6};
      }
      
      int main(){
          std::vector<int> Vec{1, 2 ,3, 4};
          foo(Vec);
      }
      

    Example 2 (builds upon example 1):

    • Simply removing the reference would not work in the code below because the deduced type carries a const qualification, (aka, T is deduced as const std::vector<int>&) the new type, RemoveReferenceT is const std::vector<int>:

      #include <vector>
      
      template<typename T>
      void foo(T&&){
          using RemovedReferenceT = std::remove_reference_t<T>;
          RemovedReferenceT nV = {3, 5, 6};
          nV[2] = 7;                               //woopsie
      }
      
      int main(){
          const std::vector<int> Vec{1, 2 ,3, 4};  //note the const
          foo(Vec);
      }
      
    • We can remove the cv qualifiers from the removed-reference's type.

      #include <vector>
      
      template<typename T>
      void foo(T&&){
          using RRT = std::remove_reference_t<T>;
          using Removed_CV_of_RRT = std::remove_cv_t<RRT>;
      
          Removed_CV_of_RRT nV = {3, 5, 6};
          nV[2] = 7;
      }
      
      int main(){
          const std::vector<int> Vec{1, 2 ,3, 4};
          foo(Vec);
      }
      

    We can go on and on, of cause, we can combine them in one line by nesting them, like: ==> using D = std::remove_cv_t<std::remove_reference_t<T>>.

    Though there is std::decay that is really powerful and short for such "combo kick" (but sometimes you want a little less of what std::decay does).