Motivation
The Boost.Asio (http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio.html) is a popular communication library with well-developed class system. This library uses two separated classes to represent network endpoints (IP address + port) for UPD and TCP protocols: udp::endpoint
and tcp::endpoint
, correspondingly.
It makes perfect sense on the communication layer where each element (udp::socket
, for example) is aware of communication protocol it works with. However, the application layer can be “protocol blind”, i.e. it can process all requests arrived via both UDP and TCP protocols in the same way.
In this case, it is useful to have a “protocol neutral” endpoint (IP + port) class. Objects of the class should be convertible and comparable with the standard udp::endpoint
and tcp::endpoint
objects. The article describes a lightweight wrapper that can serve as such generic endpoint object.
(Just in case, please notice that boost::asio::generic::basic_endpoint
is not what we are looking for.)
Description
In the Boost.Asio both standard endpoint classes, udp::endpoin
and tcp::endpoin
, are implemented as a protocol specialization of the base template ip::basic_endpoint<InternetProtocol>:
<code>
class udp
{
public:
typedef basic_endpoint<udp> endpoint;
...
};
class tcp
{
public:
typedef basic_endpoint<tcp> endpoint;
...
};
</code>
It is important that these endpoint classes are not related, and cannot be compared or converted to each other.
Fortunately, the base class basic_endpoint
defines all required endpoint functionality while the InternetProtocol
just specifies the protocol family the object will work with.
That allows us to introduce a new class derived from the basic_endpoint<none>
template specialization. This new class will have all required endpoint functionality derived from its parent, and, in the same time, it will be protocol neutrual because class <font face="Courier New">none</font>
does not represent any protocol.
That is exactly what we need. To complete the work we just should define the implicit casting operations to/from the standard endpoint classes using appropriate casting operators and constructors :
<code>
class endpoint : public boost::asio::ip::basic_endpoint<none>
{
public:
typedef boost::asio::ip::basic_endpoint<none> base_t;
endpoint() : base_t(){}
endpoint(const address_type& address, port_type port) : base_t(address, port){}
explicit endpoint(const udp::endpoint& ep) : base_t(ep.address(), ep.port()) {}
explicit endpoint(const tcp::endpoint& ep) : base_t(ep.address(), ep.port()) {}
endpoint(const endpoint& ep) : base_t(ep.address(), ep.port()) {}
template<typename T> endpoint& operator=(const T& ep)
{
BOOST_STATIC_ASSERT
(
boost::mpl::or_
<
boost::is_same<T, tcp::endpoint>::type,
boost::is_same<T, udp::endpoint>::type
>::value
);
address(ep.address());
port(ep.port());
return *this;
}
template<typename T>
const T& as() const
{
BOOST_STATIC_ASSERT
(
boost::mpl::or_
<
boost::is_same<T, tcp::endpoint>::type,
boost::is_same<T, udp::endpoint>::type
>::value
);
return reinterpret_cast<const T&>(*this);
}
template<typename T>
static const endpoint& cast(const T& other)
{
BOOST_STATIC_ASSERT
(
boost::mpl::or_
<
boost::is_same<T, tcp_endpoint>::type,
boost::is_same<T, udp_endpoint>::type
>::value
);
return reinterpret_cast<const endpoint&>(other);
}
operator const udp::endpoint&() const
{
return as<udp::endpoint>();
}
operator const tcp::endpoint&() const
{
return as<tcp::endpoint>();
}
};
</code>
It is important that the casting operations:
operator const tcp::endpoint&() const
static const endpoint& cast(const T& other)
operator const upd::endpoint&() const
do not create any temproray objects and, therefore, do not have any hidden performance costs.
The casting ctors explicit endpoint(const udp_endpoint& ep)/explicit endpoint(const udp_endpoint& ep)
are declared as explicit to avoid unintentional temproray object creation.
The <font face="Courier New">static const endpoint& cast(const T& other)</font>
function should be used instead of the casting ctor if only casting is required and no new object should be created, for example, if the endpoint
object is used as a search key.
The following are examples how the endpoint
objects can be converted/compared with objects of the tcp::endpoint
and the upd::endpoint
classes (without any hidden temproray object creation):
<code>
endpoint ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);
udp::endpoint udp_ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);
tcp::endpoint tcp_ep(boost::asio::ip::address::from_string("0.0.0.1"), 1001);
endpoint ep1(udp_ep);
ep1=(endpoint)tcp_ep;
ep1=static_cast<endpoint>(tcp_ep);
tcp::endpoint tcp_ep1=ep;
udp::endpoint udp_ep1=ep;
if(ep == udp_ep || ep == tcp_ep)
{
...
}
if(ep <= udp_ep && ep != tcp_ep)
{
...
}
typedef std::set<endpoint> ep_set_type;
typedef std::set<udp::endpoint> udp_set_type;
typedef std::set<tcp::endpoint> tcp_set_type;
ep_set_type ep_set;
udp_set_type udp_set;
udp_set_type tcp_set;
ep_set_type::iterator it=ep_set.find(endpoint::cast(udp_ep));
upd_set_type::iterator it=udp_set.find(ep);
tcp_set.erase(ep);
</code>
Platforms
- Compilers/IDE: MS Visual Studio 2015
- Boost library: 1.58.0
- Operating systems: Windows 8.1
References
History
- Initial version: 08/05/2015.