failing to deduce non-type template argument from value of different type

71 views Asked by At

I have a design which triggers a failure to deduce a non-type template parameter. I have the following minimal example:

#include <array>
#include <cstdint>

// KO see below
// using NbDim_t = uint8_t;
using NbDim_t = size_t;

// let's say it's a N dimensionnal matrix
template <NbDim_t DIM>
struct Mat {
    static constexpr NbDim_t dims = DIM;
};

// this is a matrix creator from an array of size of it's different dimensions
template <NbDim_t DIM>
Mat<DIM> Create(std::array<int, DIM> input) {
    return Mat<DIM>();
}

int main() {
    std::array<int, 3> d = {1, 2, 3};
    // KO if NbDim_t si not std::array<whatever,whatever>::size_type
    // cannot deduced a non-type template parameter of one type from a value of
    // different type
    auto M = Create(d);

    return 0;
}

Playable example

For reasons beyond the scope of this minimal example, I'd like to fine-tune the type used to represent the number of dimensions of my Mat.
Yet if I don't use size_t, DIM cannot be deduced because, at the calling point, it is of type size_t and my template function is expecting NbDim_t which is different, even if, at compile-time, the compiler could detect that the conversion would be valid.

  1. What is the exact rule of template deduction that make this design erroneous (I can't find the good spot there template argument deduction rules)?
  2. How can I fix this design (to have NbDim_t not a size_t and deduced from the std::array argument)?

[EDIT] I may have a solution for part 2:

#include <array>
#include <cstddef>
#include <cstdint>

// OK now
using NbDim_t = uint8_t;
// using NbDim_t = size_t;

// let's say it's a N dimensionnal matrix
template <NbDim_t DIM>
struct Mat {
    static constexpr NbDim_t dims = DIM;
};

// this is a matrix creator from an array of size of it's different dimensions
template <size_t N, NbDim_t DIM = N>
Mat<DIM> Create(std::array<int, N> input) {
    return Mat<DIM>();
}

int main() {
    std::array<int, 3> d = {1, 2, 3};
    // N deduced then set into DIM
    auto M = Create(d);

    return 0;
}

Live Is there a better way to do that?

1

There are 1 answers

2
Artyer On BEST ANSWER

A non-type template parameter is deduced from the template argument list of the type of a function parameter, it must have the exact same type. From the linked article on cppreference https://en.cppreference.com/w/cpp/language/template_argument_deduction#Deduction_from_a_type:

If a non-type template parameter of function template is used in the template parameter list of function parameter (which is also a template), and the corresponding template argument is deduced, the type of the deduced template argument ( as specified in its enclosing template parameter list, meaning references are preserved) must match the type of the non-type template parameter exactly

Or from the standard [temp.deduct.type]p20

If P has a form that contains <i>, and if the type of i differs from the type of the corresponding template parameter of the template named by the enclosing simple-template-id, deduction fails.

With this example given:

template<int i> class A { /* ... */ };
template<short s> void f(A<s>);
void k1() {
  A<1> a;
  f(a);             // error: deduction fails for conversion from int to short
  f<1>(a);          // OK
}

So, if you want DIM to be deduced from std::array<int, DIM>, DIM must have the type std::size_t.

You can simply let the size_t implicitly convert to NbDim_t:

template <std::size_t DIM>
Mat<DIM> Create(std::array<int, DIM> input) {
    return Mat<DIM>();
}