strongdef: significantly tighten restrictions on usage

It turns out that equality in particular was triggering implicit
construction of strongdef types. We make it much harder for these types
to spontaneously emerge.
This commit is contained in:
Danny Robson 2018-06-22 17:41:56 +10:00
parent 6c0803b6e9
commit 279af4c796
2 changed files with 87 additions and 48 deletions

View File

@ -11,14 +11,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
* Copyright 2015 Danny Robson <danny@nerdcruft.net> * Copyright 2015-2018 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef __UTIL_STRONGDEF_HPP #ifndef __UTIL_STRONGDEF_HPP
#define __UTIL_STRONGDEF_HPP #define __UTIL_STRONGDEF_HPP
#include "cast.hpp"
#include "types/traits.hpp"
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <iosfwd>
namespace util { namespace util {
/// A transparent wrapper around a (typically lightweight) type for the /// A transparent wrapper around a (typically lightweight) type for the
@ -32,63 +37,97 @@ namespace util {
// TODO: ideally we'd default the constructor, but templated // TODO: ideally we'd default the constructor, but templated
// constructors can't be defaulted? So we just stub one out. // constructors can't be defaulted? So we just stub one out.
template <typename = std::enable_if_t<std::is_default_constructible_v<T>>> template <typename = std::enable_if_t<std::is_default_constructible_v<T>>>
constexpr strongdef () { ; } constexpr explicit strongdef () { ; }
constexpr strongdef (const T &_data): data (_data) { ; } constexpr explicit strongdef (util::types::identity_t<T> const &_data): data (_data) { ; }
constexpr strongdef (strongdef const&) = default;
// explicitly disable assignment with unequal types or tags. this strongdef& operator= (T const &) = delete;
// prevents the converion operator getting invoked and falsely strongdef& operator= (strongdef const &) = default;
// allowing assignment with differing types or tags.
template <typename U, typename TagU>
std::enable_if_t<
!std::is_same<T,U>::value || !std::is_same<Tag,TagU>::value,
strongdef<T,Tag>&
>
operator= (const strongdef<U,TagU> &rhs) = delete;
template <typename U, typename TagU>
std::enable_if_t<
std::is_same<T,U>::value && std::is_same<Tag,TagU>::value,
strongdef<T,Tag>&
>
operator= (const strongdef<U,TagU> &rhs)
{ data = rhs.data; return *this; }
// simple value_type assignment.
strongdef& operator= (const T &rhs)&
{ data = rhs; return *this; }
// conversion operators must not be explicit or it defeats the point // conversion operators must not be explicit or it defeats the point
// of this class (ease of use, transparency). // of this class (ease of use, transparency).
operator const T& (void) const& { return data; } explicit operator const T& (void) const& { return data; }
operator T& (void) & { return data; } explicit operator T& (void) & { return data; }
auto const& value (void) const { return data; } constexpr auto operator== (T const &) = delete;
auto & value (void) { return data; } constexpr auto operator== (strongdef const &rhs) const
{
return data == rhs.data;
}
// explicitly disable equality with unequal types or tags. this constexpr auto operator!= (T const&) = delete;
// prevents the conversion operator getting invoked and falsely constexpr auto operator!= (strongdef const &rhs) const
// allowing equality with different types or tags. {
template <typename U, typename TagU> return data != rhs.data;
std::enable_if_t< }
!std::is_same<T, U >::value ||
!std::is_same<Tag,TagU>::value,
bool
>
operator== (const strongdef<U,TagU> &rhs) const = delete;
// provide a usable equality for equal types and tags constexpr auto operator< (T const &) const = delete;
template <typename U, typename TagU> constexpr auto operator< (strongdef const &rhs) const { return data < rhs.data; }
constexpr
std::enable_if_t< constexpr auto operator> (T const &) const = delete;
std::is_same<T, U >::value && constexpr auto operator> (strongdef const &rhs) const { return data > rhs.data; }
std::is_same<Tag,TagU>::value,
bool
>
operator== (const strongdef<U,TagU> &rhs) const { return data == rhs.data; }
T data; T data;
}; };
template <typename ValueT, typename TagT>
std::ostream&
operator<< (std::ostream &os, strongdef<ValueT,TagT> const &val)
{
return os << val.data;
}
}
template <typename ContainerT>
static auto indices (ContainerT const &obj)
{
using index_t = typename ContainerT::index_t;
struct view {
view (ContainerT const &_obj):
m_obj (_obj)
{ ; }
struct iterator {
iterator (index_t _idx):
idx (_idx)
{ ; }
iterator& operator++ ()
{
++idx.data;
return *this;
}
index_t const& operator* () const { return idx; }
index_t const* operator-> () const { return &idx; }
bool operator!= (iterator const &rhs) { return idx != rhs.idx; }
private:
index_t idx;
};
iterator begin (void) const { return iterator (index_t (0)); }
iterator end (void) const
{
return index_t (
util::cast::narrow<
typename index_t::value_type
> (
m_obj.size ()
)
);
}
private:
ContainerT const &m_obj;
};
return view {obj};
} }

View File

@ -12,7 +12,7 @@ main (void)
// These tests are less about functional testing, and more about link testing. // These tests are less about functional testing, and more about link testing.
strongdef<unsigned,void> fortytwo (42u); strongdef<unsigned,void> fortytwo (42u);
tap.expect_eq (fortytwo.data, 42u, "raw data equality"); tap.expect_eq (fortytwo.data, 42u, "raw data equality");
tap.expect_eq (fortytwo, 42u, "passthrough equality"); tap.expect_eq (fortytwo, decltype(fortytwo) (42u), "passthrough equality");
// Ensure numeric_limits has been specialised. Unknown types are default initialised, so check if we get non-zero for maximum. // Ensure numeric_limits has been specialised. Unknown types are default initialised, so check if we get non-zero for maximum.
tap.expect_eq (std::numeric_limits<decltype(fortytwo)>::max (), tap.expect_eq (std::numeric_limits<decltype(fortytwo)>::max (),