Оператор выходного потока Argument Dependent Lookup (ADL) для основных/STL-типов/классов

Я хочу преобразовать unsigned char и std::vector<unsigned char> в шестнадцатеричную строку. В настоящее время я использую выходной поток operator<< для реализации преобразования, но этот подход, похоже, имеет некоторые недостатки в отношении просмотра, зависящего от аргумента (ADL). Он просто работает только (без дополнительных действий), если я поместил два оператора в пространство имен std (см. код ниже).

Мне приходят на ум четыре подхода для реализации преобразования:

  1. Поместите оператор operator<< в то же пространство имен, что и определение типа. Проблема: компилятор не находит объявление, необходимо использовать оператор using.
  2. Поместите оператор operator<< в глобальное пространство имен. Проблема: компилятор не находит объявление, я не знаю, как это исправить.
  3. Поместите оператор operator<< в пространство имен std. Проблема: переопределяет поведение по умолчанию и кажется неправильным.
  4. Используйте какую-то оболочку/прокси в том же пространстве имен, что и определение типа. Проблема: я не могу заставить его работать с std::vector.

Описанные выше подходы реализованы в следующем исходном коде. Подходы 1. и 2. можно проверить, переместив две свободные функции operator<< из пространства имен std в другое пространство имен. Используется макрос BOOST_REQUIRE_EQUAL, поскольку он использует operator<<.

// Production code

// STL includes
#include <climits>
#include <cstdint>
#include <iomanip>
#include <ostream>
#include <vector>

// Boost includes
#include <boost/operators.hpp>
#include <boost/io/ios_state.hpp>
namespace fw {

using Byte = unsigned char;
using ByteVector = std::vector<Byte>;

// 4. Approach

class ByteWrapper final {
 public:
  ByteWrapper(Byte const kByte) : kByte_{kByte} {
    // NOOP
  }

  operator Byte() const {
    return kByte_;
  }

 private:
  Byte const kByte_;
};

inline std::ostream& operator<<(std::ostream& os, ByteWrapper const& kByte) {
  static_assert(!(CHAR_BIT & 3), "CHAR_BIT has to be a multiple of 4");
  boost::io::ios_all_saver guard{os};

  return os << std::hex << std::setfill('0') << std::uppercase
            << std::setw(CHAR_BIT >> 2) << +kByte;
}

inline ByteWrapper to_hex(Byte const kByte) {
  return {kByte};
}

class ByteVectorWrapper final
    : private boost::equality_comparable<ByteVectorWrapper> {
 public:
  ByteVectorWrapper(ByteVector const& kBytes) : kBytes_{kBytes} {
    // NOOP
  }

  // TODO(wolters): This is ugly, why can't a conversion operator be used?
  ByteVector operator() () const {
    return kBytes_;
  }

  bool operator==(ByteVectorWrapper const& kOther) const {
    return kOther.kBytes_ == kBytes_;
  }

 private:
  ByteVector const kBytes_;
};

inline std::ostream& operator<<(std::ostream& os,
                                ByteVectorWrapper const& kVector) {
  for (auto i = 0; i < (kVector().size() - 1); ++i) {
    os << to_hex(kVector()[i]) << ' ';
  }

  return os << to_hex(kVector()[kVector().size() - 1]);
}

inline ByteVectorWrapper to_hex(ByteVector const& kByteVector) {
  return {kByteVector};
}

}  // namespace fw

// 3. Approach I don't think this a correct approach, since the default
// behavior of the fundamental data type `unsigned char` and the STL template
// class `std::vector<_Tp, _Alloc>` is overwritten!

namespace std {

inline std::ostream& operator<<(std::ostream& os, fw::Byte const& kByte) {
  static_assert(!(CHAR_BIT & 3), "CHAR_BIT has to be a multiple of 4");
  boost::io::ios_all_saver guard{os};

  return os << std::hex << std::setfill('0') << std::uppercase
            << std::setw(CHAR_BIT >> 2) << +kByte;
}

inline std::ostream& operator<<(std::ostream& os,
                                fw::ByteVector const& kBytes) {
  for (auto i = 0; i < (kBytes.size() - 1); ++i) {
    // Calls `operator<<(std::ostream&, fw::Byte const&)`.
    os << kBytes[i] << ' ';
  }

  return os << kBytes[kBytes.size() - 1];
}

}  // namespace std

// Test code

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MAIN

#include <iostream>

#include <boost/test/unit_test.hpp>

namespace {

// If the two operators would have been placed in the `fw` namespace, one of the
// following lines would be required:
//using namespace fw;
//using fw::operator<<;

BOOST_AUTO_TEST_CASE(OutputStreamOperator_Byte) {
  fw::Byte const kByte{0xA};
  std::cout << kByte << '\n';
  std::cout << fw::to_hex(kByte) << '\n';
  BOOST_REQUIRE(true);
}

BOOST_AUTO_TEST_CASE(OutputStreamOperator_ByteVector) {
  fw::ByteVector const kByteVector{0xA, 0x0, 0xF, 0x9};
  std::cout << kByteVector << '\n';
  std::cout << fw::to_hex(kByteVector) << '\n';
  BOOST_REQUIRE(true);
}

BOOST_AUTO_TEST_CASE(OutputStreamOperator_Byte_Equal) {
  fw::ByteVector const kFirstByte{0xA};
  fw::ByteVector const kSecondByte{kFirstByte};

  BOOST_REQUIRE_EQUAL(kFirstByte, kSecondByte);
  BOOST_REQUIRE_EQUAL(fw::to_hex(kFirstByte), fw::to_hex(kSecondByte));
}

BOOST_AUTO_TEST_CASE(OutputStreamOperator_ByteVector_Equal) {
  fw::ByteVector const kFirstByteVector{0xA, 0x0, 0xF, 0x9};
  fw::ByteVector const kSecondByteVector{kFirstByteVector};

  // TODO(wolters): Raises a GCC compiler error if using approach 1. or 2.
  // error: no match for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'const std::vector<unsigned char>')
  // ostr << t; // by default print the value
  //      ^

  BOOST_REQUIRE_EQUAL(kFirstByteVector, kSecondByteVector);
  BOOST_REQUIRE_EQUAL(fw::to_hex(kFirstByteVector), fw::to_hex(kSecondByteVector));
}

}  // namespace

Что вы думаете? Каков хороший подход, чтобы реализовать то, что я хочу? Вообще не полагаться на операторы выходного потока и использовать явные бесплатные функции? Что насчет пространства имен? Если использовать операторы, в какое пространство имен я должен их поместить и почему?

Я использую С++ 11 с GCC 4.7.1 и Boost 1.49.


person Florian Wolters    schedule 29.07.2015    source источник
comment
[namespace.std]/1 Поведение программы C++ не определено, если она добавляет объявления или определения в пространство имен std или в пространство имен внутри пространства имен std, если не указано иное. По этой причине ваш подход № 3 демонстрирует UB.   -  person Igor Tandetnik    schedule 29.07.2015
comment
Спасибо @Игорь Тандетник. Для полноты здесь цитируется раздел 17.6.4.2.1 Международного стандарта ISO/IEC 14882:2014(E) — язык программирования C++.   -  person Florian Wolters    schedule 29.07.2015
comment
Я не понимаю вопроса, но вообще, если это возможно, было бы неплохо иметь friend ostream& operatotr<<(ostream& os, Class const& c> (который можно определить внутри класса).   -  person alfC    schedule 09.01.2016