Зацикливание шаблона через кортеж
Я играю с вариадическими шаблонами и сейчас пытаюсь реализовать 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>());
}