C++ doesn't have proper intersection types. But you can fake it a bit with compile-time template logic.
Say I define some capabilities as mixins:
struct cap_a {
void foo();
};
struct cap_b {
void bar();
};
struct cap_c {
void baz();
};
And a container like this that inherits from all the capabilities:
template<typename... TCaps>
struct intersectable : public TCaps... {};
I can create an intersection of two intersectable
's with a mix of SFINAE and variadic templates. The resulting type only has capabilities that both inputs have.
First step of doing a cross-join is to check if a type is in a list. This is a pretty basic variadic macro. Each iteration pops off a type, checks if its the target type, and or's it with the next iteration.
template<typename TCap, typename... TCaps>
struct has_capability_impl;
template<typename TCap, typename TCapFirst, typename... TCaps>
struct has_capability_impl<TCap, TCapFirst, TCaps...> {
static constexpr bool value = std::is_convertible<TCap, TCapFirst>::value || has_capability_impl<TCap, TCaps...>::value;
};
template<typename TCap>
struct has_capability_impl<TCap> {
static constexpr bool value = false;
};
template<typename TCap, typename... TCaps>
constexpr bool has_capability = has_capability_impl<TCap, TCaps...>::value;
You can do this with fold expressions in C++17, but this works in older versions.
Next is the intersection. This a template with 3 types: An empty result itersectable, the left hand side, and branches with enable_if. If the type is present on the right intersectable, move the type over to the result in the inherited base type. OTherwise, don't.
For each iteration, pop a type off a
template<typename TLRhs, typename TLhs, typename TRhs, typename = void>
struct intersect_impl;
template<typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<TFirst, TLCaps...>, intersectable<TRCaps...>, std::enable_if_t<has_capability<TFirst, TRCaps...>>> : intersect_impl<intersectable<TLRCaps..., TFirst>, intersectable<TLCaps...>, intersectable<TRCaps...>> { };
template<typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<TFirst, TLCaps...>, intersectable<TRCaps...>, std::enable_if_t<!has_capability<TFirst, TRCaps...>>> : intersect_impl<intersectable<TLRCaps...>, intersectable<TLCaps...>, intersectable<TRCaps...>> { };
By the time there are no types left on the left hand side, the result only has capabilities present from both sides:
template<typename... TLRCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<>, intersectable<TRCaps...>> {
using type = intersectable<TLRCaps...>;
};
template<typename TLhs, typename TRhs>
using intersection = typename intersect_impl<intersectable<>, TLhs, TRhs, void>::type;
Package that in a trivial function to combine instances:
template<typename TLhs, typename TRhs>
intersection<TLhs, TRhs> intersect(TLhs lhs, TRhs rhs) {
return intersection<TLhs, TRhs>{}; // runtime link logic goes here
}
...and the result is type-safe capability intersections:
int main() {
intersectable<cap_a, cap_c> ac;
ac.foo();
ac.baz();
intersectable<cap_a, cap_b> ab;
ab.foo();
ab.bar();
auto a = intersect(ac, ab);
a.foo();
a.bar(); // error
a.baz(); // error
}
Demo: https://godbolt.org/z/-kd2Qj
Again, this doesn't actually do anything, it just intersects the types. But you can use something like this to make your linked list logic type-safe.
Anyway, to add range checking, it's just a matter of working in compile-time properties for range into the definition of intersectable
#include <cstdint>
#include <functional>
#include <type_traits>
#include <unordered_set>
struct cap_a {
void foo();
};
struct cap_b {
void bar();
};
struct cap_c {
void baz();
};
template<int Min, int Max, typename... TCaps>
struct intersectable : public TCaps... {
};
template<typename TCap, typename... TCaps>
struct has_capability_impl;
template<typename TCap, typename TCapFirst, typename... TCaps>
struct has_capability_impl<TCap, TCapFirst, TCaps...> {
static constexpr bool value = std::is_convertible<TCap, TCapFirst>::value || has_capability_impl<TCap, TCaps...>::value;
};
template<typename TCap>
struct has_capability_impl<TCap> {
static constexpr bool value = false;
};
template<typename TCap, typename... TCaps>
constexpr bool has_capability = has_capability_impl<TCap, TCaps...>::value;
template<typename TLRhs, typename TLhs, typename TRhs, typename = void>
struct intersect_impl;
template<int LRMin, int LRMax, int LMin, int LMax, int RMin, int RMax, typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TFirst, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>,
std::enable_if_t<(has_capability<TFirst, TRCaps...>)>>
: intersect_impl<
intersectable<LRMin, LRMax, TLRCaps..., TFirst>,
intersectable<LMin, LMax, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>> { };
template<int LRMin, int LRMax, int LMin, int LMax, int RMin, int RMax, typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TFirst, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>,
std::enable_if_t<(!has_capability<TFirst, TRCaps...>)>>
: intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>> { };
template<int LMin, int LMax, int RMin, int RMax, typename TResult, typename... TRCaps>
struct intersect_impl<TResult, intersectable<LMin, LMax>, intersectable<RMin, RMax, TRCaps...>> {
using type = TResult;
};
template <typename T>
struct props;
template<int Min, int Max, typename... Caps>
struct props<intersectable<Min, Max, Caps...>> {
static constexpr int min = Min;
static constexpr int max = Max;
};
template<typename TLhs, typename TRhs>
using intersection = typename intersect_impl<
intersectable<(std::max(props<TLhs>::min, props<TRhs>::min)), (std::min(props<TLhs>::max, props<TRhs>::max))>,
TLhs,
TRhs>::type;
template<typename TLhs, typename TRhs>
intersection<TLhs, TRhs> intersect(TLhs lhs, TRhs rhs) {
static_assert((props<TLhs>::max >= props<TRhs>::min && props<TLhs>::min <= props<TRhs>::max) ||
(props<TRhs>::max >= props<TLhs>::min && props<TRhs>::min <= props<TLhs>::max), "out of range");
return intersection<TLhs, TRhs>{}; // runtime intersection logic?
}
int main() {
intersectable<1, 5, cap_a, cap_c> ac;
ac.foo();
ac.baz();
intersectable<3, 8, cap_a, cap_b> ab;
ab.foo();
ab.bar();
auto a = intersect(ac, ab); // result is a intersectable<3, 5, cap_a>
a.foo();
a.bar(); // error
a.baz(); // error
intersectable<10, 15, cap_a, cap_b> ab_out_of_range;
auto a0 = intersect(ac, ab_out_of_range);
}
demo: https://godbolt.org/z/zz9-Vg