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

Overriding operator<< for all types

本文关键字:lt 类型 适用于 覆盖 运算符      更新时间:2023-10-16

我对尝试编写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";
}

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

一般来说你不能

。 但这是 c++,所以如果你愿意作恶,你可以。

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

我认为这不值得。

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

此瘦包装器可以具有通用模板运算符<<成员,这反过来又检查包装的 std::ostream 上的真实运算符。但是,您可能会发现收到更不愉快的错误消息!

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