I have a template class like this:
A minimal implementation looks like this:
template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
struct Int {
using NT = T;
T v;
explicit constexpr Int(T v) noexcept : v{v} {}
template<typename U, std::enable_if_t<std::is_integral_v<U>, bool> = true>
constexpr auto cast() const noexcept -> Int<U> {
return Int<U>{static_cast<U>(v)};
}
template<typename U, typename U::NT = 0>
constexpr auto cast() const noexcept -> Int<typename U::NT> {
return Int<typename U::NT>{static_cast<typename U::NT>(v)};
}
};
Then, there are a number of predefined names for types of this class:
using Int8 = Int<int8_t>;
using Int16 = Int<int16_t>;
using Int32 = Int<int32_t>;
using Int64 = Int<int64_t>;
Now I can use this class natually:
int main(int argc, const char *argv[]) {
auto a = Int32{10};
auto b = a.cast<int64_t>();
auto c = a.cast<Int64>();
}
I would like to limit the second cast
method only to values of the Int
class.
My current approach will work with any class that has a type definition called NT
in its namespace. As in my library NT
is commonly used, this does not limit the usage of the cast
method enough for my liking.
struct Unrelated {
using NT = int32_t;
};
int main(int argc, const char *argv[]) {
auto a = Int32{10};
auto b = a.cast<Unrelated>(); // NO! is confusing, shouldn't work.
}
Is there a commonly used approach to "enable" a method only with template instances of the own class?
- I am aware there are simple solutions in C 2x. Yet, I need a solution that is working with C 17.
- The first cast method, accepting all integral types shall stay intact.
CodePudding user response:
First a type trait from Igor Tandetnik (my own was uglier):
template<typename T> struct Int; // forward declaration
template <typename T> struct is_Int : std::false_type {};
template <typename T> struct is_Int<Int<T>> : std::true_type {};
template <typename T> inline constexpr bool is_Int_v = is_Int<T>::value;
Then you could define the class and its cast
like so:
template<typename T>
struct Int {
static_assert(std::is_integral_v<T>); // no SFINAE needed so use static_assert
using NT = T;
T v;
explicit constexpr Int(T v) noexcept : v{v} {}
template<class U> // accept all, no SFINAE needed
constexpr auto cast() const noexcept {
// use static_assert instead
static_assert(std::is_integral_v<U> || is_Int_v<U>); // using the trait
// ...and constexpr-if:
if constexpr (std::is_integral_v<U>) return Int<U>{static_cast<U>(v)};
else return U{static_cast<typename U::NT>(v)};
}
};