Search code examples
c++recursionvariadic-templatesvariadic-functionsfunction-templates

Overloading the End of Recursion for a Variable Length Template Function


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?


Solution

  • 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);
    }