Search code examples
c++templatesc++17template-meta-programmingenable-if

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


I have a template class that represents a special integer type.

A minimal implementation of this class could look like this:

template<typename T>
struct Int {
    static_assert(std::is_integral_v<T>, "Requires integral type.");
    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)};
    }
};

There are a number of predefined type names for most common use-cases of the class:

using Int8 = Int<int8_t>;
using Int16 = Int<int16_t>;
using Int32 = Int<int32_t>;
using Int64 = Int<int64_t>;

The goal is to use the types of this class naturally, yet with a set of methods. One of these methods is a .cast<>() method to convert between the underlying integer types:

int main(int argc, const char *argv[]) {
    auto a = Int32{10};
    auto b = a.cast<int64_t>();
    auto c = a.cast<Int64>();
}

To cover a wide range of uses, by the user and programatically in templates, the cast template argument shall allow the native type and also the template class as argument. Specifying int64_t, Int64 or therefore Int<int64_t> shall lead to the exact same result.

I would like to limit the second cast method to values of the Int template class.

The approach shown in the example, will work with any class that has a type definition called NT in its namespace. As in my library NT is commonly used in template classes, it does not limit the usage of the cast method enough for my liking.

The following example illustrates a case I would like to avoid:

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.

Solution

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