覆盖运算符<<适用于所有类型

时间:2017-10-23 16:33:03

标签: c++ c++11 operator-overloading overloading name-lookup

当我尝试编写std::cout << x并且左移位运算符没有为x定义时,我对编译错误感到有点恼火。无法将x转换为此,无法将x转换为...无效错误消息的几个屏幕。

我想为所有尚未定义此类运算符的类型专门化operator<<(std::ostream&, const T&)。在内部,我可以将静态断言和编译错误消息放得比现在更清晰。

我的第一次尝试如下。

template<typename T, typename = void>
struct Has : public std::false_type {};

template<typename T>
struct Has<T, decltype(void(
            std::declval<std::ostream&>() << std::declval<T>()
))> : public std::true_type {};

template<typename T>
auto operator<<(std::ostream& out, const T&)
    -> typename std::enable_if<
        !Has<T>::value,
        std::ostream&>::type
{
    return out << "my operator";
}

无法编译,因为超出了最大模板深度。确实,我的operator<<调用Has专门化,以便调用operator<<,再次检查我的重载,依此类推,等等。

最简单的版本不起作用:std::ostream& << const char*的模糊重载。好吧,期待。

template<typename T>
std::ostream& operator<<(std::ostream& out, const T&)
{
    return out << "my operator";
}

我该如何完成任务?或者,一般来说,如何为所有参数类型定义函数,但是对于那些已经传递给函数的函数?

2 个答案:

答案 0 :(得分:1)

一般情况下你不能。但这是,所以如果你愿意变得邪恶,你就可以。

namespace named_operator {
  template<class D>struct make_operator{constexpr make_operator(){}};

  template<class T, char, class O> struct half_apply { T&& lhs; O const& o; };

  template<class Lhs, class Op>
  half_apply<Lhs, '<', Op> operator<( Lhs&& lhs, make_operator<Op>const & o ) {
    return {std::forward<Lhs>(lhs), o};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator<( half_apply<Lhs, '<', Op>&& lhs, Rhs&& rhs )
  -> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), lhs.o, std::forward<Rhs>(rhs) ) )
  {
    return named_invoke( std::forward<Lhs>(lhs.lhs), lhs.o, std::forward<Rhs>(rhs) );
  }
}
namespace utility {
  namespace details {
    template<class...>struct voider{using type=void;};
  }
  template<class...Ts>using void_t=typename details::voider<Ts...>::type;
  namespace details {

    template<template<class...>class, class, class...>
    struct can_apply:std::false_type{};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = details::can_apply<Z,void,Ts...>;
}
namespace streaming {
  namespace details {
    template<class T>
    using ostream_r = decltype( std::cout << std::declval<T&&>() );
  }
  template<class T>
  using can_ostream = utility::can_apply<details::ostream_r, T>;

  struct out_tag: named_operator::make_operator<out_tag> {};
  static const out_tag out;
  template<class T>
  std::ostream& named_invoke( std::ostream& os, out_tag, T const& t ) {
    static_assert( can_ostream<T const&>{}, "This type cannot be streamed" );
    return os<<t;
  }
  template<class T,
    std::enable_if_t< can_ostream<T const&>{}, int> =0 // breaks MSVC
  >
  std::ostream& named_invoke( std::ostream& os, out_tag, T const& t ) {
    return os<<t;
  }
}

如果我写得对,

struct no_worky {};
no_worky bob;
using streaming::out;
std::cout <out< bob;

无法编译并生成友好消息,同时

std::cout <out< 7;

致电std::cout << 7

我认为这不值得。

答案 1 :(得分:0)

一个答案可能是将ostream包裹在一个薄的包装中以进行管道操作?

这个薄包装器可以具有通用模板操作符&lt;&lt;会员, 然后检查包装的std :: ostream上的真实运算符。 但是,您可能会发现您收到的错误信息更加令人愉快!

请注意,您还需要为std :: endl及其亲属添加一个特定的处理程序,因为他们希望专注于您已经混淆的流宽度。