Can C++ infer type of non-type template argument and enforce the type on the rest of template arguments?

90 Views Asked by At

Consider this struct. It is a range, but uses template instead of constructor, making the values static:

template <int min_v, int max_v>
struct num_range final {
  constexpr static int min = min_v;
  constexpr static int max = max_v;
}
// now you can do
std::cout << num_range<1,3>::min << '\n'; // prints 1

But this is fixed to int. What if I wanted C++ to deduce the numeric type from the first argument and enforce it on the second argument?

I tried this:

template <typename TNumber>
template <TNumber min_v, TNumber max_v>
struct num_range final 

I tried that at the reverse order. I know you can do this:

template <auto min_v, decltype(min_v) max_v>
struct num_range final {
    using num_type = decltype(min_v);
    constexpr static num_type min = min_v;
    constexpr static num_type max = max_v;
};

But I don't think auto template argument is allowed in C++17, which is what I am targeting. It also does not seem to enforce the type on the second template argument, as this compiles just fine:

std::cout << num_range<-10, 12U>::min << '\n'; // prints -10

Bonus points if you know how to do this with variadric template, enforcing all numbers to be same type.

2

There are 2 best solutions below

0
HolyBlackCat On BEST ANSWER

I don't think auto template argument is allowed in C++17

It is allowed in C++17.

It also does not seem to enforce the type on the second template argument

Yes, some implicit conversions are permitted when passing template arguments. Do this instead:

template <auto min_v, auto max_v>
struct num_range
{
    static_assert(std::is_same_v<decltype(min_v), decltype(max_v)>);
    constexpr static auto min = min_v;
    constexpr static auto max = max_v;
};

Or in C++20:

template <auto min_v, auto max_v>
requires std::is_same_v<std::is_same_v<decltype(min_v), decltype(max_v)>
struct num_range
{
    constexpr static auto min = min_v;
    constexpr static auto max = max_v;
};
4
Jan Schultke On

Your original approach does work, you just haven't made it noticeable:

using range = num_range<static_cast<signed char>(0), 200>;

clang rejects this:

<source>:8:54: error: non-type template argument evaluates to 200, which cannot be narrowed to type 'decltype('\x00')' (aka 'signed char') [-Wc++11-narrowing]
    8 | using range = num_range<static_cast<signed char>(0), 200>;
      |   

In your example num_range<-10, 12U>, 12U can be converted to int (the type of -10), so everything compiles fine.

Admittedly, this behavior is surprising, since -10 and 12U are still different types and it's intuitively wrong that num_range<-10, 12U> compiles, but implicit conversions make it work. If you want to prevent this, you will have deduce both types separately and then check whether they are the same (@HolyBlackCat has offered two solutions for this).

But I don't think auto template argument is allowed in C++17

It is, see cppreference: Template parameters.

Bonus points if you know how to do this with variadric template, enforcing all numbers to be same type.

template <auto Head, decltype(Head)... Tail>
/* ... */

Or, similar to @HolyBlackCat's solution, using std::enable_if:

template <auto Head, auto... Tail, std::enable_if_t<(std::is_same_v<decltype(Head), decltype(Tail)> && ...), int> = 0>
/* ... */