5

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?

max66
  • 65,235
  • 10
  • 71
  • 111
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

3 Answers3

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);
}
max66
  • 65,235
  • 10
  • 71
  • 111
1

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...>(...);
}
super
  • 12,335
  • 2
  • 19
  • 29
1

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

NathanOliver
  • 171,901
  • 28
  • 288
  • 402