#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?
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
}
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).