Home > database >  How to limit parameter less template method to types of the own template class?
How to limit parameter less template method to types of the own template class?

Time:08-28

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)};
    }
};
  • Related