Есть ли законный способ печати кортежей и пар с помощью оператора <<?
У меня есть набор шаблонов / функций, которые позволяют мне печатать кортеж / пару, предполагая, что каждый тип в кортеже / паре имеет operator<<
определено для этого. К сожалению, из-за 17.4.3.1, это незаконно, чтобы добавить мой operator<<
перегрузки в std
, Есть ли другой способ заставить ADL найти мой operator<<
? Если нет, то есть ли какой-нибудь реальный вред в обертывании моей перегрузки в namespace std{}
?
Код для всех, кто интересуется: (я использую gcc-4.5)
namespace tuples {
using ::std::tuple;
using ::std::make_tuple;
using ::std::get;
namespace detail {
template< typename...args >
size_t size( tuple<args...> const& )
{
return sizeof...(args);
};
template<size_t N>
struct for_each_ri_impl
{
template<typename Func, typename Tuple>
void operator()(Func func, Tuple const& arg)
{
for_each_ri_impl<N-1>()(func, arg );
func( get<N>( arg ), size(arg) - N - 1 );
}
};
template<>
struct for_each_ri_impl<0>
{
template<typename Func, typename Tuple>
void operator()(Func func, Tuple const& arg)
{
func( get<0>( arg ), size(arg) - 1 );
}
};
}//detail
template<typename Func, typename ... Args>
void for_each_ri( tuple<Args...>const& tup, Func func )
{
detail::for_each_ri_impl< sizeof...(Args)-1>()( func, tup );
}
struct printer {
std::ostream& out;
const std::string& str;
explicit printer( std::ostream& out=std::cout, std::string const& str="," ) : out(out), str(str) { }
template<typename T>void operator()(T const&t, size_t i=-1) const { out<<t; if(i) out<<str; }
};
//Should this next line go into namespace std? Is there another way?
template<typename ... Args>
std::ostream& operator<<(std::ostream& out, std::tuple< Args... > const& tup)
{
out << '[';
tuples::for_each_ri( tup, tuples::printer(out,", ") );
return out << ']';
}
} //tuples
//Edits --
int main()
{
using namespace std;
cout<<make_tuple(1,'a',"Hello")<<endl;
return 0;
}
Компиляция вышеупомянутых результатов:
test.cpp: в функции 'int main ()':
test.cpp: 69: 31: ошибка: невозможно привязать значение 'std::ostream' lvalue к 'std::basic_ostream&&' > /opt/local/include/gcc45/ C++/ostream:579:5: ошибка: инициализация аргумента 1 из 'std:: basic_ostream<_CharT, _Traits>& std:: operator<< (std:: basic_ostream <_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits, _Tp = std:: кортежа]
3 ответа
Поместите свой собственный класс light wrapper вокруг него, а затем перегрузите оператор<<, чтобы использовать это. Однако имейте в виду, что даже если ваша легкая обертка имеет неявный конструктор, вам, вероятно, все равно придется использовать ее явно, когда вы передаете ее оператору <<
template< typename ...VA_ARGS >
struct format_tuple
{
typedef tuple<VA_ARGS...> tuple_type;
// any format variables
const tuple_type & tup;
format_tuple( const tuple_type& t): tup(t) {}
};
template< typename ...VA_ARGS > format_tuple<VA_ARGS...> makeFormatTuple( const tuple<VA_ARGS...> & t )
{
return format_tuple( t );
}
template<typename ...VA_ARGS>
std::ostream& operator<<( std::ostream& os, const format_tuple<VA_ARGS...> & ft )
{
// original implementation
}
Это набросок, так как я не уверен, как именно это сделать с помощью шаблонов с переменными значениями, хотя это должно быть возможно. Вы можете легко реализовать несколько версий, используя параметры 1, 2, 3 и т. Д., Например:
template<typename T1, typename T2, typename T3>
class format_tuple_3; //etc
template<typename T1, typename T2, typename T3>
format_tuple_3<T1, T2, T3> makeFormatTuple( tuple<T1,T2,T3> const&); //etc
Вред - кто-то другой (например, в сторонней библиотеке, которую вы хотите использовать), также добавляющий эти объявления в std. Даже если они ведут себя одинаково, вы нарушите ODR.
Просто поместите их в пространство имен вашего проекта:
namespace kitsune_ymg {
// Op<< overloads here.
// Your "normal" stuff.
void normal_stuff() {
std::cout << std::pair<int, int>(42, 3);
}
И тогда все в вашем проекте может их использовать.
Я до сих пор не знаю точно, почему это не работает для вас, но, кажется, вы хотите что-то вроде:
namespace kitsune_ymg {
namespace tuples {
// Op<< overloads here.
}
using namespace tuples;
// Your "normal" stuff.
}
namespace completely_separate_project {
using kitsune_ymg::tuples;
// Now you can use those op<< overloads in this scope, too.
void perfectly_normal_beast() {
std::cout << std::pair<int, int>(42, 3);
}
}
Вы не должны добавлять свои
operator<<
к
std
. Однако вы можете написать адаптер для кортежей или один для потоков и использовать его с минимальным изменением сайтов вызовов.
Я предполагаю, что C++17 или новее (чтобы использовать структурированные привязки и выражения свертки), хотя вопрос, очевидно, намного старше.
Адаптировать кортеж
#include <ostream>
#include <tuple>
template<typename... Args>
struct printable_tuple
{
typedef std::tuple<Args...> tuple_type;
const tuple_type& t;
// implicit converting constructor
printable_tuple(const tuple_type& t)
: t(t)
{}
};
template<typename... Args>
std::ostream& operator<<(std::ostream& os, const printable_tuple<Args...>& tuple)
{
const char *sep = "";
os << '[';
std::apply([&os,&sep](auto&&...args){((os << sep << args, sep = ","),...);}, tuple.t);
return os << ']';
}
#include <iostream>
int main()
{
std::cout << format_tuple{std::tuple{1,'a',"Hello"}} << '\n';
}
Это наименее навязчивый вариант, поскольку мы можем использовать возвращаемый поток в обычном режиме (
if (os << tuple)
, например), но для этого требуется обернуть каждый аргумент.
Адаптируйте поток
#include <tuple>
template<typename Stream>
class tuple_ostream
{
Stream& os;
public:
// conversions from and to Stream
tuple_ostream(Stream& os) : os{os} {}
operator Stream&() const { return os; };
// generic forwarding <<
template<typename T>
tuple_ostream& operator<<(const T&t)
{
os << t;
return *this;
}
// overload for tuples
template<typename... Args>
tuple_ostream& operator<<(const std::tuple<Args...>&t)
{
const char *sep = "";
os << '[';
std::apply([this,&sep](auto&&...args){((os << sep << args, sep = ","),...);}, t);
os << ']';
return *this;
}
};
#include <iostream>
int main()
{
tuple_ostream{std::cout} << std::tuple{1,'a',"Hello"} << '\n';
}
Адаптация потока, очевидно, проще, когда нам нужно записать несколько кортежей в один и тот же поток, но мы больше не можем напрямую использовать возвращенный поток в качестве исходного, если мы не добавим больше функций в оболочку.
Подсказка к ответу CashCow за отправную точку для этого.