Overloading the End of Recursion for a Variable Length Template Function

时光怂恿深爱的人放手 提交于 2019-12-10 14:58:13

问题


François Andrieux gave me a good workaround for this Visual Studio 2017 problem. I was trying to build on his answer like so:

template<class T, size_t N>
ostream& vector_insert_impl(ostream& lhs, const char*, const T& rhs)
{
    return lhs << at(rhs, N);
}

template<class T, size_t N, size_t... I>
ostream& vector_insert_impl(ostream& lhs, const char* delim, const T& rhs)
{
    return vector_insert_impl<T, I...>(lhs << at(rhs, N) << delim, delim, rhs);
}

template <typename T, size_t... I>
ostream& vector_insert(ostream& lhs, const char* delim, const T& rhs, index_sequence<I...>) 
{
    return vector_insert_impl<T, I...>(it, delim, rhs);
}

The key difference is that the "end of recursion" templated function actually inserts the last value into the ostream, and not the delimiter rather than being a no-op. But when I try to compile this I get the error:

error C2668: vector_insert_impl: ambiguous call to overloaded function (compiling source file ....\src\STETestbed\Test.cpp)
note: could be std::ostream &vector_insert_impl<T,2,>(std::ostream &,const char *,const T &)
note: or std::ostream &vector_insert_impl<T,2>(std::ostream &,const char *,const T &)

I thought variable length template functions were considered 3rd class citizens and fixed length template functions would always be preferred. That preference doesn't appear to be in effect here. Is there a workaround which will force the compiler to choose my "end of recursion" function enabling me to avoid inserting the delimiter?


回答1:


Is there a workaround which will force the compiler to choose my "end of recursion" function enabling me to avoid inserting the delimiter?

You can add the "Next" element

template <typename T, std::size_t N>
std::ostream & vector_insert_impl (std::ostream & lhs, char const *, T const & rhs)
{
    return lhs << at(rhs, N);
}

// ..................................vvvvvvvvvvvvvvvv
template <typename T, std::size_t N, std::size_t Next, std::size_t ... I>
std::ostream & vector_insert_impl (std::ostream & lhs, char const * delim, T const & rhs)
{ // ............................vvvv
    return vector_insert_impl<T, Next, I...>(lhs << at(rhs, N) << delim, delim, rhs);
}

but, if you can use C++17, I suppose if constexpr is a better solution

template <typename T, std::size_t N, std::size_t ... Is>
std::ostream & vector_insert_impl (std::ostream & lhs, char const * delim, T const & rhs)
{
   if constexpr ( sizeof...(Is) )
      return vector_insert_impl<T, Is...>(lhs << at(rhs, N) << delim, delim, rhs);
   else
      return lhs << at(rhs, N);
}



回答2:


The simplest solution to avoid ambigous calls is to add an additional size_t to the second overload. That way it can only be called with at least 2 parameters, and the 1 parameter case will fall down to your first overload.

template<class T, size_t N, size_t N2,  size_t... ARGS>
ostream& vector_insert_impl(ostream& lhs, const char* delim, const T& rhs)
{
    return vector_insert_impl<T, N2, I...>(...);
}



回答3:


variable length template functions are not considered 3rd class citizens. When you have

template<class T, size_t N>
ostream& vector_insert_impl(ostream& lhs, const char*, const T& rhs)

and

template<class T, size_t N, size_t... I>
ostream& vector_insert_impl(ostream& lhs, const char* delim, const T& rhs)

then when you have 1 value left you have a choice between the first function and the last function with an empty pack. There is no preference for either, so you have an ambiguous call. They way to fix this is to make it to where the variadic case can't be called if the pack is empty. You can do that by adding a second non-type parameter so that it will only be called if there are 2 or more values like

template<class T, size_t N, size_t M, size_t... I>
ostream& vector_insert_impl(ostream& lhs, const char* delim, const T& rhs)
{
    return vector_insert_impl<T, M, I...>(lhs << at(rhs, N) << delim, delim, rhs);
}

So now when I is empty you have vector_insert_impl<T, M> is what is called and the only overload that is valid for that is the first one.


What are 3rd class citizens is the old C style variadic functions. void foo(int, ...) will not be ambiguous with void foo(int, int).



来源:https://stackoverflow.com/questions/56653029/overloading-the-end-of-recursion-for-a-variable-length-template-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!