Переопределяющий оператор‹‹ для всех типов

Меня немного раздражают ошибки компиляции, которые возникают, когда я пытаюсь написать 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";
}

Как я могу выполнить задание? Или вообще, как определить функцию для всех типов аргументов, кроме тех, которые уже могут быть переданы в функцию?


person Ivan Smirnov    schedule 23.10.2017    source источник
comment
Парадокс парикмахера   -  person Igor Tandetnik    schedule 23.10.2017
comment
@IgorTandetnik Не совсем так. С точки зрения парикмахера это выглядит так: «Сегодня вечером парикмахер побреет всех, кто еще не побрился». Я хочу определить функцию для всех типов, которые не принимаются функцией в другом месте. Я не вижу здесь никакого парадокса.   -  person Ivan Smirnov    schedule 23.10.2017
comment
Было бы трудно, а то и невозможно использовать такой оператор в правильно построенной программе. Определение вашего оператора, если вам удастся его реализовать, будет зависеть от того, какие именно специализации будут видны в момент его создания. Которые могут отличаться в разных единицах перевода и даже в пределах одной единицы перевода (на [temp.point]/8), что будет нарушением ODR, что приведет к неправильному формату программы и не потребует диагностики. .   -  person Igor Tandetnik    schedule 23.10.2017
comment
@IgorTandetnik О, ну... Спасибо за ссылку, почитаю стандарт и либо пойму абсурдность своего вопроса, либо переформулирую.   -  person Ivan Smirnov    schedule 23.10.2017
comment
stackoverflow.com/questions/15912283/ не является прямым ответом, но охватывает некоторые из тех же вопросов. Обходной путь, при котором вы каким-то образом делаете свой << менее привлекательным, а не тестируете <<, может сработать; в качестве альтернативы вы можете написать свой собственный оператор потоковой передачи, который использует <<, если он существует, а в противном случае генерирует статическое утверждение.   -  person Yakk - Adam Nevraumont    schedule 23.10.2017
comment
Как насчет использования другого имени? template <typename T> std::enable_if_t<Has<T>::value> print(std::ostream& out, const T&t) {return out << t;} и аналогичные для std::enable_if_t<!Has<T>::value>.   -  person Jarod42    schedule 23.10.2017
comment
@ Jarod42 Нет. Я хочу использовать <<, но будьте осторожны, если я случайно использую его с несовместимым типом. Я попытался аргументировать это в комментариях к ответу.   -  person Ivan Smirnov    schedule 23.10.2017
comment
И Yakk предлагает примерно то же самое в ответ на ваш комментарий ;-)   -  person Jarod42    schedule 23.10.2017


Ответы (2)


В общем нельзя. Но это 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.

Я не думаю, что это того стоит.

person Yakk - Adam Nevraumont    schedule 23.10.2017
comment
Я только что изучил эти именованные операторы по ссылке из вашего профиля :) Это действительно классная вещь с темной стороны C++. Хотя это не решает мою проблему правильно. На самом деле, вот объяснение: я занимаюсь конкурентоспособным программированием, и те секунды, которые вы тратите на выяснение того, почему ваш код не компилируется, бесценны. Поэтому мне нужно не решение производственного качества, а специальный хак, который предупредит меня о cout-ing std::vector и не должен принимать во внимание ODR, множественные единицы перевода и тому подобное. Вопрос, на который вы ссылаетесь, может помочь, я изучаю его в данный момент. - person Ivan Smirnov; 23.10.2017
comment
@IvanSmirnov Тогда напишите print(ostream&, Args const&...) вместо <<? - person Yakk - Adam Nevraumont; 23.10.2017

Один из ответов может заключаться в том, чтобы обернуть ostream в тонкую обертку для работы с конвейером?

Эта тонкая оболочка может иметь член универсального оператора шаблона ‹‹, который, в свою очередь, проверяет наличие реальных операторов в обернутом std::ostream. Однако вы можете обнаружить, что получили еще менее приятное сообщение об ошибке!

Обратите внимание, что вам также необходимо добавить специальный обработчик для std::endl и его аналога, поскольку они предполагают специализацию по ширине потока, которую вы запутали.

person Gem Taylor    schedule 23.10.2017