Зацикливание шаблона через кортеж

Я играю с вариадическими шаблонами и сейчас пытаюсь реализовать operator<< для кортежа.

Я пробовал следующий код, но он не компилируется (GCC 4.9 с -std= C++11).

template<int I, typename ... Tlist>
void print(ostream& s, tuple<Tlist...>& t)
{
    s << get<I>(t) << ", ";
    if(I < sizeof...(Tlist)){
        print<I+1>(s,t);
    }
}
template<typename ... Tlist>
ostream& operator<<(ostream& s, tuple<Tlist...> t)
{
    print<0>(s,t);
    return s;
}

Сообщение об ошибке очень загадочное и длинное, но в основном говорит, что для get нет соответствующего вызова функции. Может кто-нибудь объяснить мне, почему? Благодарю.

РЕДАКТИРОВАТЬ: Вот шаблон экземпляр я использую

auto t = make_tuple(5,6,true,"aaa");
cout << t << endl;

2 ответа

Решение

Код в if (blah) { блок } компилируется и должен быть действительным, даже если условие blah ложно

template<bool b>
using bool_t = std::integral_constant<bool, b>;

template<int I, typename ... Tlist>
void print(std::ostream& s, std::tuple<Tlist...> const& t, std::false_type) {
  // no more printing
}

template<int I, typename ... Tlist>
void print(std::ostream& s, std::tuple<Tlist...> const& t, std::true_type) {
  s << std::get<I>(t) << ", ";
  print<I+1>(s, t, bool_t<((I+1) < sizeof...(Tlist))>{});
}
template<typename ... Tlist>
std::ostream& operator<<(std::ostream& s, std::tuple<Tlist...> const& t)
{
  print<0>(s,t, bool_t<(0 < sizeof...(Tlist))>{});
  return s;
}

должно сработать. Здесь мы используем диспетчеризацию тегов, чтобы контролировать, какую перегрузку мы вызываем рекурсивно: третий аргумент true_type если I является допустимым индексом для кортежа, и false_type если не. Мы делаем это вместо if заявление. Мы всегда повторяем, но когда мы достигаем конца кортежа, мы возвращаемся к завершающей перегрузке.

живой пример

Кроме того, это неоднозначно, если перегрузка << для двух типов, определенных в std соответствует стандарту: зависит std::tuple<int> это "определенный пользователем тип" или нет, пункт, который стандарт не определяет.

Кроме того, рекомендуется перегрузить операторы для типа в пространстве имен этого типа, поэтому его можно найти через ADL. Но, перегрузка << внутри std является недопустимым по стандарту (вы не можете вводить новые перегрузки в std). Результатом может быть несколько неожиданное поведение в некоторых случаях, когда обнаружена неправильная перегрузка или перегрузка не обнаружена.

Вы должны использовать специализацию или SFINAE в качестве ветви, даже если она не берется, генерирует экземпляр:

template<int I, typename ... Tlist>
void print(ostream& s, tuple<Tlist...>& t)
{
    s << get<I>(t) << ", ";
    if(I < sizeof...(Tlist)){
        print<I+1>(s,t); // Generated even if I >= sizeof...(Tlist)
    }
}

И поэтому у вас будет бесконечное воплощение print если get<sizeof...(Tlist)> не выдает ошибку раньше.

Вы можете написать это без рекурсии с:

template<std::size_t ... Is, typename Tuple>
void print_helper(std::ostream& s, const Tuple& t, std::index_sequence<Is...>)
{
    int dummy[] = { 0, ((s << std::get<Is>(t) << ", "), 0)...};
    (void) dummy; // remove warning for unused var
}


template<typename Tuple>
void print(std::ostream& s, const Tuple& t)
{
    print_helper(s, t, std::make_index_sequence<std::tuple_size<Tuple>::value>());
}

Живой пример

Другие вопросы по тегам