Reputation: 921
The following code specializes two versions of f(). The first detects a vector and returns an iterator. The second takes all other types and returns a copy.
This fails to compile on VC 2010 with an error in GetIter2 that GetIter::type doesn't exist. This is only when resolving a call to f() with a non-vector. If I remove one layer of type indirection, using GetIter instead of GetIter2 (see commented line) for the return type, then things work fine.
I guess I want to know if this a compiler bug or correct behavior. If this looks weird it's because it's a reduction of a problem I'm having using boost::range_iterator where I can't just remove what GetIter2 represents.
#include <vector>
using namespace std;
template<typename T>
struct GetIter {
};
template<typename T>
struct GetIter<vector<T>> {
typedef typename vector<T>::iterator type;
};
template<typename T>
struct GetIter2
{
typedef typename GetIter<T>::type type;
};
template<typename T>
typename enable_if<is_same<T, vector<int>>::value, typename GetIter2<T>::type>::type
//typename enable_if<is_same<T, vector<int>>::value, typename GetIter<T>::type>::type
f(T & t) {
return t.begin();
}
template<typename T>
typename enable_if<!is_same<T, vector<int>>::value, T>::type
f(T & t) {
return t;
}
int main(int argc, char* argv[])
{
vector<int> v(2);
int i = 6;
f(v);
f(i); // error C2039: 'type' : is not a member of 'GetIter<T>'
return 0;
}
EDIT: Here is the actual problem I'm trying to solve. The second call to copy() with an iterator as the second argument causes a similar error as above on the boost::mpl::eval_if_c object.
#include <vector>
using namespace std;
#include <boost/range.hpp>
#include <boost/tti/has_type.hpp>
BOOST_TTI_TRAIT_HAS_TYPE(has_iterator, iterator)
template<typename InCont, typename Out>
typename enable_if<has_iterator<Out>::value, typename boost::range_iterator<Out>::type>::type
copy(InCont const & in_cont, Out & out_cont)
{
return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}
template<typename InCont, typename Out>
typename enable_if<!has_iterator<Out>::value, Out>::type
copy(InCont const & in_cont, Out & out_iter)
{
return std::copy(boost::begin(in_cont), boost::end(in_cont), out_iter);
}
int main(int argc, char* argv[])
{
vector<int> v1;
vector<int> v2;
copy(v1, v2);
copy(v1, v2.begin()); // error C2039: 'type' : is not a member of 'boost::mpl::eval_if_c<C,F1,F2>'
return 0;
}
EDIT 2: The original problem was fixed in a recent version of boost::range_iterator. Once I patched that things got easier. Here's what I've landed on, checking for containers with boost::has_range_iterator:
#include <vector>
using namespace std;
#include <boost/range.hpp>
template<typename InCont, typename Out>
typename boost::range_iterator<Out>::type
copy(InCont const & in_cont, Out & out_cont)
{
return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}
template<typename InCont, typename Out>
typename enable_if<!boost::has_range_iterator<Out>::value, Out>::type
copy(InCont const & in_cont, Out out_iter)
{
return std::copy(boost::begin(in_cont), boost::end(in_cont), out_iter);
}
int main(int argc, char* argv[])
{
vector<int> v1;
vector<int> v2;
copy(v1, v2);
copy(v1, v2.begin());
return 0;
}
Upvotes: 1
Views: 107
Reputation: 4034
I'm still trying to find the relevant part of the standard to explain why this doesn't work. I think it has something to do with the substitution of GetIter2<T>
not failing but subsequently accessing GetIter2<T>::type
where it then determines that GetIter<T>::type
doesn't exist. By making the GetIter2<T>
template fail to substitute right away you don't get the error. You can do that by changing its definition slightly:
template<typename T, typename ST = typename GetIter<T>::type>
struct GetIter2
{
typedef ST type;
};
I was able to get your real example working without modifying boost types with the following code:
template<typename InCont, typename Out, typename = typename enable_if<has_iterator<Out>::value>::type>
typename boost::range_iterator<Out>::type
copy(InCont const & in_cont, Out & out_cont)
{
return std::copy(boost::begin(in_cont), boost::end(in_cont), boost::begin(out_cont));
}
Upvotes: 1
Reputation: 303457
Why it doesn't work
The rule is, from [temp.deduct], emphasis mine:
Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.
The immediate context is what you see in the declaration itself. The failure that happens when you try to substitute T=int
isn't in the immediate context of typename GetIter2<T>::type
- the failure happens when trying to determine what that type actually is and seeing that GetIter<int>
doesn't have a type
member. Since that isn't in the immediate context, this isn't a deduction failure - it's a hard error.
Note: even if this were a deduction failure, it would still only work for std::vector<int, std::allocator<int>>
, not any vector.
How to make it work
Actually, you don't need SFINAE at all here. Just have one overload for vector
and another overload for everything:
template <class T, class A>
auto f(std::vector<T,A>& t) {
return t.begin();
}
template <class T>
T f(T& t) {
return t;
}
Upvotes: 1
Reputation: 1
I think the non-vector GetIter missed a type function. template struct GetIter { typedef T type; };
Upvotes: -1