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
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
* Copyright 2015-2018 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_STRONGDEF_HPP
#define __UTIL_STRONGDEF_HPP
#include "cast.hpp"
#include "types/traits.hpp"
#include <limits>
#include <type_traits>
#include <iosfwd>
namespace util {
/// 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
// constructors can't be defaulted? So we just stub one out.
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
// prevents the converion operator getting invoked and falsely
// 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; }
strongdef& operator= (T const &) = delete;
strongdef& operator= (strongdef const &) = default;
// conversion operators must not be explicit or it defeats the point
// of this class (ease of use, transparency).
operator const T& (void) const& { return data; }
operator T& (void) & { return data; }
explicit operator const T& (void) const& { return data; }
explicit operator T& (void) & { return data; }
auto const& value (void) const { return data; }
auto & value (void) { return data; }
constexpr auto operator== (T const &) = delete;
constexpr auto operator== (strongdef const &rhs) const
{
return data == rhs.data;
}
// explicitly disable equality with unequal types or tags. this
// prevents the conversion operator getting invoked and falsely
// allowing equality with different types or tags.
template <typename U, typename TagU>
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;
constexpr auto operator!= (T const&) = delete;
constexpr auto operator!= (strongdef const &rhs) const
{
return data != rhs.data;
}
// provide a usable equality for equal types and tags
template <typename U, typename TagU>
constexpr
std::enable_if_t<
std::is_same<T, U >::value &&
std::is_same<Tag,TagU>::value,
bool
>
operator== (const strongdef<U,TagU> &rhs) const { return data == rhs.data; }
constexpr auto operator< (T const &) const = delete;
constexpr auto operator< (strongdef const &rhs) const { return data < rhs.data; }
constexpr auto operator> (T const &) const = delete;
constexpr auto operator> (strongdef const &rhs) const { return data > rhs.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.
strongdef<unsigned,void> fortytwo (42u);
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.
tap.expect_eq (std::numeric_limits<decltype(fortytwo)>::max (),