9

我有一组模板/函数,允许我打印一个元组/对,假设元组/对中的每种类型都已为其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'左值绑定到'std::basic_ostream&&'> /opt/local/include/gcc45/c++ /ostream:579:5: 错误:初始化 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) 的参数 1 [with _CharT = char , _Traits = std::char_traits, _Tp = std::tuple]'


危害是其他人(例如在您要使用的第三方库中)也将这些声明添加到 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);
}
}
4

3 回答 3

2

将您自己的轻量级包装类放在它周围,然后重载 operator<< 以使用它。但是请注意,即使您的轻包装器具有隐式构造函数,当您将其传递给 operator<< 时,您可能仍需要显式使用它

    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
于 2011-02-22T09:58:05.877 回答
2

危害是其他人(例如在您要使用的第三方库中)也将这些声明添加到 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);
}
}
于 2011-02-22T09:42:48.067 回答
0

您不得将自己的添加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 的答案致敬,以此作为起点。

于 2021-03-07T11:19:24.140 回答