Skip to content

Static vs Runtime type overloads #229

@ktbarrett

Description

@ktbarrett

After some playing around with things I finally discovered you can do the following:

#include <type_traits>
#include <concepts>
#include <cstddef>
#include <limits>
#include <tuple>

enum class Logic {
    _0, _1, X, Z
};

enum class Direction {
    TO, DOWNTO,
};

struct Range {
    using value_type = int;
    int left;
    Direction dir;
    int right;
};

namespace detail {

// Build a static Range from the trailing template arguments of Array<T, ...>.
//   1 arg, integral N                -> Range(N)              i.e. {0, TO, N-1}
//   1 arg, Range R                   -> R
//   2 args, (left, right)            -> Range{left, right}    direction inferred
//   3 args, (left, Direction, right) -> Range{left, dir, right}
// Compile-time check that an integral NTTP value fits in
// Range::value_type without silent narrowing. Used by the 2/3-arg branches
// of make_static_range below to give a clean diagnostic instead of a
// silently-truncating static_cast.
template <auto V>
constexpr bool fits_range_value_type =
    static_cast<long long>(V) >= std::numeric_limits<Range::value_type>::min()
    && static_cast<long long>(V) <= std::numeric_limits<Range::value_type>::max();

template <auto... Args>
constexpr Range make_static_range() {
    using std::get;
    static_assert(
        sizeof...(Args) >= 1 && sizeof...(Args) <= 3,
        "Array<T, ...> takes 0 args (dynamic) or 1-3 range args (static)"
    );
    constexpr auto t = std::tuple{Args...};
    if constexpr (sizeof...(Args) == 1) {
        using First = std::remove_cvref_t<decltype(get<0>(t))>;
        static_assert(
            std::is_same_v<First, Range> || std::integral<First>,
            "single template arg must be a Range value or an integral length"
        );
        if constexpr (std::is_same_v<First, Range>) {
            return get<0>(t);
        } else {
            static_assert(get<0>(t) >= 0, "Array<T, N>: N (length) must be non-negative");
            return Range(static_cast<size_t>(get<0>(t)));
        }
    } else if constexpr (sizeof...(Args) == 2) {
        static_assert(
            fits_range_value_type<get<0>(t)>,
            "Array<T, L, H>: L does not fit in Range::value_type (int32_t)"
        );
        static_assert(
            fits_range_value_type<get<1>(t)>,
            "Array<T, L, H>: H does not fit in Range::value_type (int32_t)"
        );
        return Range{
            static_cast<Range::value_type>(get<0>(t)),
            static_cast<Range::value_type>(get<1>(t))
        };
    } else {  // 3
        static_assert(
            std::is_same_v<std::remove_cvref_t<decltype(get<1>(t))>, Direction>,
            "three-arg form requires (left, Direction, right)"
        );
        static_assert(
            fits_range_value_type<get<0>(t)>,
            "Array<T, L, D, H>: L does not fit in Range::value_type (int32_t)"
        );
        static_assert(
            fits_range_value_type<get<2>(t)>,
            "Array<T, L, D, H>: H does not fit in Range::value_type (int32_t)"
        );
        return Range{
            static_cast<Range::value_type>(get<0>(t)),
            get<1>(t),
            static_cast<Range::value_type>(get<2>(t))
        };
    }
}}

template <typename T, Range R>
class StaticArray {};

template <typename T, auto... Args>
class Array : StaticArray<T, detail::make_static_range<Args...>()>{
};

template <typename T>
class Array<T> {
};

template <auto... Args>
class LogicArray : StaticArray<Logic, detail::make_static_range<Args...>()>{
};

template <>
class LogicArray<> : Array<Logic> {
};


int main()
{
    Array<int> a;
    LogicArray b;  // no empty brackets like previous implementation
    LogicArray<Range{1, Direction::TO, 8}> c;
    return 0;
}

Ultimately the issue is that template argument deduction isn't available when using the previous approach with template aliases so you had to add empty <> after dynamic bounded types, which was annoying. But with this we can re-unify them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions