Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 964
xmllmx Avatar asked Feb 23 '17 07:02

xmllmx


People also ask

What is forwarding reference in C++?

When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.

What is universal reference C++?

In a type declaration, “ && ” indicates either an rvalue reference or a universal reference – a reference that may resolve to either an lvalue reference or an rvalue reference. Universal references always have the form T&& for some deduced type T .


1 Answers

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).

like image 94
WhiZTiM Avatar answered Nov 12 '22 02:11

WhiZTiM