基本/ STL类型/类的输出流运算符Argument Dependent Lookup(ADL)

时间:2015-07-29 07:21:15

标签: c++ c++11 stl operator-overloading argument-dependent-lookup

我想将unsigned charstd::vector<unsigned char>转换为十六进制字符串。目前我正在使用输出流operator<<来实现转换,但这种方法似乎在 Argument Dependent Lookup(ADL)方面存在一些缺点。如果我将两个运算符放在std命名空间中(参见下面的代码),那么只能正常工作(无需其他操作)。

我想到了四种方法来实现转换:

  1. 将运算符operator<<放在与类型定义相同的命名空间中。 问题:编译器找不到声明,必须使用using语句。
  2. 将运算符operator<<放在全局命名空间中。 问题:编译器找不到声明,我不知道如何解决这个问题。
  3. 将运算符operator<<放在std命名空间中。 问题:覆盖默认行为并感觉不对。
  4. 在与类型定义相同的命名空间中使用某种包装器/代理。 问题:我无法使用std::vector
  5. 以上方法通过以下源代码实现。可以通过将operator<<命名空间中的两个std自由函数移动到另一个命名空间来测试方法1.和2.使用宏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
    
    你怎么看?什么是好方法,实现我想要的?根本不依赖输出流操作符并使用显式自由函数?命名空间怎么样?如果使用运算符,我应该在哪个命名空间中添加它们?为什么?

    我正在使用C ++ 11和GCC 4.7.1以及Boost 1.49。

0 个答案:

没有答案