1

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?

Oersted
  • 769
  • 16
  • 2
    `size_t` lives in `std::`. You need to use `std::size_t` or have a line before with `using std::size_t;` – NathanOliver Jun 20 '23 at 15:56
  • @NathanOliver-IsonStrike I never had to scope size_t when using ``. Can it be an issue? It seems from [this post](https://stackoverflow.com/questions/5813700/difference-between-size-t-and-stdsize-t) that the standard guarantees that there is no difference. Yet It also says that `` puts `size_t` inside `std`, yet no compiler has ever complained... Is there a reason to use the more verbose version? – Oersted Jun 20 '23 at 16:10
  • 1
    `` defines `std::size_t`. It may also define `::size_t`, but it is not required. IF you want your code to be portable, use `std::size_t`. – NathanOliver Jun 20 '23 at 16:15
  • Yes, your comment has triggered a bit of reading on my side and it occurred to me that it extends to all the symbols that existed in C and that have been kept in C++: (std::)(u)intx_t, (std::)malloc,... – Oersted Jun 20 '23 at 16:21
  • If you for example had included `` in your example, it would have polluted the global namespace with the definition of `::size_t` and then you wouldn't have noticed that you used it "wrongly". – Ted Lyngmo Jun 20 '23 at 16:22
  • Yet I didn't ever see a coding rules set that enforce that. But I understand it should be so as these non `std::` versions could be removed in the future. Besides there is edge cases where there is a real difference (for instance in `cmath` `abs` does not do the same thing as `std::abs` in terms of argument type handling. – Oersted Jun 20 '23 at 16:24
  • while it is more typing, using the fully qualified name dramatically reduces the amount of bugs that can happen. It doesn't prevent you from not including the correct header, but it does guarantee you'll get the thing you expected or a hard error. – NathanOliver Jun 20 '23 at 16:25
  • 1
    @NathanOliver-IsonStrike I'm on my way for a long night of search and replace in all my codes ;-). I don't know if I should thank you ;-) – Oersted Jun 20 '23 at 16:25
  • With C++20, you can use `template ` if you want deduction to work with any integer type. – François Andrieux Jun 20 '23 at 16:44

1 Answers1

1

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>();
}
Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Thanks for the part 1 answer. For part 2, even if your proposal seems simpler than mine, my solution has the advantage to provide a free compile-time check on the size of the array. Thus a cleaner declaration may be `template ::size_type N, NbDim_t DIM = N>`. If `N` cannot fit into a `NbDim_t`the compiler will issue an error. – Oersted Jun 20 '23 at 19:27
  • Yet, if I need to control the array size, maybe that a `static_assert` inside the body function, for instance, could be more readable... – Oersted Jun 20 '23 at 19:28