I want to convert an unsigned char
and a std::vector<unsigned char>
to a hexadecimal string. Currently I am using the output stream operator<<
to realize the conversion, but that approach seems to have some drawbacks regarding Argument Dependent Lookup (ADL). It just works only (without additional actions) if I put the two operators in the std
namespace (see code below).
Four approaches come to my mind, to realize the conversion:
- Put operator
operator<<
in the same namespace as the type definition. Problem: The compiler doesn't find the declaration, ausing
statement has to be used. - Put operator
operator<<
in the global namespace. Problem: The compiler doesn't find the declaration, I don't know how-to fix that. - Put operator
operator<<
in thestd
namespace. Problem: Overrides the default behavior and feels wrong. - Use some kind of wrapper/proxy in the same namespace as the type definition. Problem: I can't make it work with
std::vector
.
The above approaches are realized in the following source code. Approaches 1. and 2. can be tested by moving the two operator<<
free functions in the std
namespace to another namespace. The macro BOOST_REQUIRE_EQUAL
is used, because it utilizes 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
What do you think? What is a good approach, to realize what I want? Do not rely on the output stream operators at all and use explicit free functions? What about the namespacing? If using operators, in which namespace should I put them and why?
I am using C++11 with GCC 4.7.1 and Boost 1.49.
Aucun commentaire:
Enregistrer un commentaire