Перегрузка конца рекурсии для функции шаблона переменной длины - PullRequest
5 голосов
/ 18 июня 2019

Франсуа Андрие дал мне хороший обходной путь для этой проблемы Visual Studio 2017 .Я пытался построить его ответ следующим образом:

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

Ключевым отличием является то, что шаблонная функция «конец рекурсии» фактически вставляет последнее значение в ostream, , а неразделитель вместо того, чтобы быть неоператором.Но когда я пытаюсь это скомпилировать, я получаю сообщение об ошибке:

ошибка C2668: vector_insert_impl: неоднозначный вызов перегруженной функции (компиляция исходного файла .... \ src \ STETestbed \ Test.cpp)
примечание: может быть std::ostream &vector_insert_impl<T,2,>(std::ostream &,const char *,const T &)
примечание: или std::ostream &vector_insert_impl<T,2>(std::ostream &,const char *,const T &)

Я думал, что функции шаблона переменной длины рассматривались как 3 rd граждан класса и шаблон фиксированной длиныфункции всегда будут предпочтительнее.Это предпочтение, кажется, не действует здесь.Есть ли обходной путь, который заставит компилятор выбрать мою функцию «конец рекурсии», позволяющую избежать вставки разделителя?

Ответы [ 3 ]

1 голос
/ 18 июня 2019

Самое простое решение, позволяющее избежать неоднозначных вызовов, состоит в добавлении дополнительного size_t ко второй перегрузке.Таким образом, он может быть вызван только по крайней мере с 2 параметрами, и регистр с 1 параметром упадет до вашей первой перегрузки.

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...>(...);
}
1 голос
/ 18 июня 2019

шаблонные функции переменной длины не считаются гражданами 3-го класса. Когда у вас есть

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

и

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

тогда, когда у вас осталось 1 значение, у вас есть выбор между первой функцией и последней функцией с пустой упаковкой. Нет предпочтения ни одному, поэтому у вас неоднозначный вызов. Они могут решить эту проблему, чтобы сделать так, чтобы переменная не могла быть вызвана, если пакет пуст. Вы можете сделать это, добавив второй нетипичный параметр, чтобы он вызывался только при наличии 2 или более значений, таких как

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

Так что теперь, когда I пусто, у вас есть vector_insert_impl<T, M> - это то, что вызывается, и единственная перегрузка, которая действительна для этого, является первой.


Что такое граждане 3-го класса, это старые функции в стиле Си. void foo(int, ...) не будет неоднозначным с void foo(int, int).

1 голос
/ 18 июня 2019

Есть ли обходной путь, который заставит компилятор выбрать мою функцию «конец рекурсии», позволяющую мне избежать вставки разделителя?

Вы можете добавить «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);
}

но, если вы можете использовать C ++ 17, я думаю, if constexpr является лучшим решением

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