Довольно печатать для всех классов с поддержкой диапазона для цикла

Я хочу реализовать симпатичную печать для всех классов с поддержкой для поддержки цикла цикла с помощью перегрузки < <. Код (неправильный) выглядит следующим образом.

template<class C> ostream& operator<<(ostream& out, const C& v) {
  for(auto x : v) out<<x<<' ';
  return out;
}

Проблема заключается в том, что это будет противоречить существующим < перегрузка. Есть ли способ указать в шаблоне, что C должен поддерживать ranged-base для цикла?

Ответ 1

Поскольку для циклов, основанных на диапазонах, требуется begin(v) и end(v), чтобы быть действительными с ADL (и std::, являющимся ассоциированным пространством имен), вы можете использовать это:

namespace support_begin_end
{
    // we use a special namespace (above) to
    // contain the using directive for 'std':
    using namespace std;

    // the second parameter is only valid
    // when begin(C()) and end(C()) are valid
    template<typename C,
        typename=decltype(begin(std::declval<C>()),end(std::declval<C>()))
    >
    struct impl
    {
        using type = void; // only here impl
    };

    // explicitly disable conflicting types here
    template<>
    struct impl<std::string>;
}

// this uses the above to check for ::type,
// which only exists if begin/end are valid
// and there is no specialization to disable it
// (like in the std::string case)
template<class C,typename = typename supports_begin_end::impl<C>::type>
std::ostream& operator<<(std::ostream& out, const C& v) {
    for(auto x : v) out<<x<<' ';
    return out;
}

Живой пример

Существуют и другие типы, которые подходят для циклов, основанных на диапазонах. Не знаю, нужно ли их обнаруживать.


Здесь приведен обновленный живой пример, который определяет оба контейнера/типы, поддерживающие begin(v)/end(v), а также типы, которые поддерживают v.begin()/v.end().

Ответ 2

SFINAE:

template<class C>
auto operator<<(std::ostream& out, const C& v) -> decltype(v.begin(), v.end(), (out))
//                                          or -> decltype(std::begin(v), std::end(v), (out))
{
    for (auto x : v)
        out << x << ' ';
  return out;
}

Ответ 3

Общий комментарий по всем ответам:

Использование

for (auto x : v)

будет КОПИРОВАТЬ все элементы из коллекции перед их печатью, в результате чего будет много вызовов конструктора копирования и деструктора. Вероятно, вам лучше с

for (auto &x : v)

как ваш цикл.