c++arraystemplatesclang++sfinae

c++ template supress inmplicit conversion of bounded array to pointer type?


I am creating some simple utility template functions to help when writing binary data to a file. Many file formats have large rather static headers (300kb in my case) so I want one of the overloads to take a const bounded array. The overload should extract the extent from the array.

However, it appears the compiler is implicitly converting the bounded array to a generic pointer, which of course no longer has length information so can't be expected to write the correct number of bytes. The other overload, that is explicitly not applicable to array types, however, does seem to keep the correct bounded array type (and is correctly ignored).

Plot twist: it does compile with an explicit template argument of the bounded array, even if via decltype. That seems odd and annoying. Anyone know of a change I could make to the template to encourage it to be given the bounded array implicitly?

#include <type_traits>
#include <stdio.h>
#include <stdint.h>

/*line 5*/ template<typename T, std::enable_if_t<!std::is_array<T>::value, bool> Z = true> void write(FILE* fout, const T& data) {
  fwrite(reinterpret_cast<const void*>(&data), sizeof(T), 1, fout);
}

/*line 9*/ template<typename T, std::enable_if_t<std::is_bounded_array_v<T>, size_t> L = std::extent_v<T>> void write(FILE* fout, T t) {
  fwrite(reinterpret_cast<const void*>(t), sizeof(typename std::remove_extent<T>::type) * L, 1, fout);
}

const uint8_t header[] = { 1, 2, 3, 4 };

int main() {
  FILE* f = fopen("temp.bin", "w");
  /*line 17*/ write(f, header); //error
  write<decltype(header)>(f, header);//compiles
  fclose(f);
}

compiler results:

$ clang++ bounded_array_template.cpp -o bounded_array_template --std=c++20
bounded_array_template.cpp:17:16: error: no matching function for call to 'write'
  /*line 17*/  write(f, header); //error
               ^~~~~
bounded_array_template.cpp:5:97: note: candidate template ignored: requirement '!std::is_array<unsigned char [4]>::value' was not satisfied [with T = unsigned char [4]]
/*line 5*/ template<typename T, std::enable_if_t<!std::is_array<T>::value, bool> Z = true> void write(FILE* fout, const T& data) {
                                                                                                ^
bounded_array_template.cpp:9:113: note: candidate template ignored: requirement 'std::is_bounded_array_v<const unsigned char *>' was not satisfied [with T = const unsigned char *]
/*line 9*/ template<typename T, std::enable_if_t<std::is_bounded_array_v<T>, size_t> L = std::extent_v<T>> void write(FILE* fout, T t) {
                                                                                                                ^
1 error generated.

Emphasis:

requirement '!std::is_array<unsigned char [4]>::value' was not satisfied [with T = unsigned char [4]]

requirement 'std::is_bounded_array_v<const unsigned char *>' was not satisfied [with T = const unsigned char *]

seems to be only picking the interpretation that breaks the template requirement.

Obviously I could just make a macro like write_array(file, array) write<decltype(array)>(file array) but that kinda defeats the point of overloading.

Ideally I want a change I could make to line 9 to make line 17 compile.


Solution

  • It is not necessary to use SFINAE: the function can be rewritten by overloading the second signature to accept a reference to a bounded array type.

    template <typename T>
    void write(FILE* fout, const T& data)
    { fwrite(reinterpret_cast<const void*>(&data), sizeof(T), 1, fout); }
    
    template<typename T, std::size_t N>
    void write(FILE* fout, T (&data)[N])
    { fwrite(reinterpret_cast<const void*>(data), sizeof(std::remove_extent_t<T>) * N, 1, fout); }