I've recently picked up a book that gives various "modern" C++ challenges/solutions. One of the first ones I did was on modeling an IPv4 address in C++. Below is the full implementation; it's also on Github. Any suggestions? My goal is to make this as modern as possible and I'm not sure if there are some C++17 features I'm not taking advantage of.
ip_address.h
#pragma once
#include <array>
#include <string>
#include <string_view>
#include <stdint.h>
namespace ip
{
/**
* @brief Thrown when there is an invalid ip address passed via
* string.
*/
class invalid_format_exception : public std::exception
{
std::string invalid_format_;
public:
invalid_format_exception(const std::string &invalid_format);
char const* what() const override;
};
/**
* Class that models a IPv4 address.
*/
class address
{
public:
#pragma region Type definitions
using value_type = uint8_t;
using reference = value_type & ;
using pointer = value_type * ;
using iterator = std::array<value_type, 4>::iterator;
using const_iterator = std::array<value_type, 4>::const_iterator;
using reverse_iterator = std::array<value_type, 4>::reverse_iterator;
using const_reverse_iterator = std::array<value_type, 4>::const_reverse_iterator;
using size_type = std::array<value_type, 4>::size_type;
#pragma endregion
/**
* @brief Create an IP address representation from the
* four parts of the address definition.
* @param first the first part of the address
* @param second the second part of the address
* @param third the third part of the address.
* @param fourth the fourth part of the address.
* @details Example:
* @code
* ip::address addr(127, 0, 0, 1);
* @endcode
*/
address(const value_type& first, const value_type &second,
const value_type &third, const value_type& fourth);
/**
* @brief Create an IP address representaiton from an
* array.
* @param data the data array.
* @details Example:
* @code
* ip::address addr = {127, 0, 0, 1};
* @endcode
*/
address(const std::array<unsigned char, 4> &data);
/**
* @brief Create an IP adderss representation from a
* unsigned 32 bit integer.
* @param value the integer representation of an IP address.
*/
explicit address(const uint32_t &value);
/**
* @brief Implicit conversion to an unsigned 32 bit integer.
*/
uint32_t operator()() const;
/**
* @brief Access operator.
* @param index the index to access.
*/
reference operator[](const int &index) noexcept(false);
/**
* @brief Const version of the access operator.
*/
value_type operator[](const int &index) const noexcept(false);
/**
* @brief Prefix increment operator.
*/
void operator++();
/**
* @brief Postfix increment operator.
*/
::ip::address& operator++(int);
/**
* @brief Prefix decrement operator.
*/
void operator--();
/**
* @brief Prefix decrement operator.
*/
::ip::address& operator--(int);
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
private:
std::array<value_type, 4> data_;
};
bool operator<(const ip::address &first, const ip::address &second);
bool operator==(const ip::address &first, const ip::address &second);
std::ostream& operator<<(std::ostream& output, const ip::address &address);
address from_string(const std::string &view);
std::string to_string(const address& address);
}
ip_address.cpp
#include <ip_address.h>
#include <iterator>
#include <iostream>
#include <sstream>
#include <regex>
#include <vector>
#include <string>
#pragma region Utilities
template<typename Output>
void split(const std::string &s, char delim, Output result) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
*(result++) = item;
}
}
std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
split(s, delim, std::back_inserter(elems));
return elems;
}
#pragma endregion
ip::invalid_format_exception::invalid_format_exception(const std::string& invalid_format)
: invalid_format_(invalid_format)
{
}
char const* ip::invalid_format_exception::what() const
{
std::ostringstream oss;
oss << "Invalid IP address format: " << invalid_format_;
return oss.str().c_str();
}
ip::address::address(const value_type & first, const value_type & second, const value_type & third, const value_type & fourth)
{
data_[0] = first;
data_[1] = second;
data_[2] = third;
data_[3] = fourth;
}
ip::address::address(const std::array<unsigned char, 4>& data)
{
data_ = data;
}
ip::address::address(const uint32_t& value)
{
data_[0] = value >> 24 & 0xFF;
data_[1] = value >> 16 & 0xFF;
data_[2] = value >> 8 & 0xFF;
data_[3] = value & 0xFF;
}
uint32_t ip::address::operator()() const
{
const uint32_t value = data_[0] << 24 | data_[1] << 16 | data_[2] << 8 | data_[3];
return value;
}
ip::address::reference ip::address::operator[](const int& index)
{
return data_.at(index);
}
ip::address::value_type ip::address::operator[](const int& index) const
{
return data_.at(index);
}
void ip::address::operator++()
{
auto location = std::find_if(data_.rbegin(), data_.rend(), [](const unsigned char& data)
{
return data < 255;
});
if(location != std::rend(data_))
{
const auto r_index = std::distance(data_.rbegin(), location);
auto index = 4 - r_index - 1;
data_[index]++;
}
}
::ip::address& ip::address::operator++(int)
{
auto result(*this);
++(*this);
return result;
}
void ip::address::operator--()
{
auto location = std::find_if(data_.rbegin(), data_.rend(), [](const unsigned char& data)
{
return data < 255;
});
if (location != std::rend(data_))
{
const auto r_index = std::distance(data_.rbegin(), location);
auto index = 4 - r_index - 1;
data_[index]--;
}
}
::ip::address& ip::address::operator--(int)
{
auto result(*this);
--(*this);
return result;
}
ip::address::iterator ip::address::begin()
{
return data_.begin();
}
ip::address::const_iterator ip::address::end() const
{
return data_.end();
}
bool ip::operator<(const ip::address& first, const ip::address& second)
{
return (uint32_t)first() < (uint32_t)second();
}
bool ip::operator==(const ip::address& first, const ip::address& second)
{
return (uint32_t)first() == (uint32_t) second();
}
ip::address::const_iterator ip::address::begin() const
{
return data_.begin();
}
ip::address::iterator ip::address::end()
{
return data_.end();
}
std::ostream& ip::operator<<(std::ostream& output, const ip::address& address)
{
std::copy(address.begin(), address.end()-1,
std::ostream_iterator<short>(output, "."));
output << +address[3];
return output;
}
ip::address ip::from_string(const std::string &view)
{
auto parts = split(view, '.');
if (parts.size() != 4)
{
throw invalid_format_exception(view);
}
return {
(ip::address::value_type)std::stoi(parts[0]),
(ip::address::value_type)std::stoi(parts[1]),
(ip::address::value_type)std::stoi(parts[2]),
(ip::address::value_type)std::stoi(parts[3])
};
}
std::string ip::to_string(const address& address)
{
std::ostringstream string_stream;
string_stream << address;
return string_stream.str();
}