Search code examples
c++templatesconst-reference

Why can't the .size() of an array passed to a function as a constant reference be used as a template parameter?


I can't seem to figure out why the following code doesn't work:

#include <array>

template <long unsigned int s> void a() {}

template <long unsigned int s> void b(const std::array<int, s>& arr) {
    a<arr.size()>(); // error: no matching function for call to 'a'
}


int main() {
    const std::array<int, 2> arr {{0, 0}};
    a<arr.size()>(); // Works
    b<arr.size()>(arr);
    return 0;
}

GCC fails with the following:

test.cpp: In instantiation of ‘void b(const std::array<int, s>&) [with long unsigned int s = 2]’:
test.cpp:13:22:   required from here
test.cpp:6:18: error: no matching function for call to ‘a<(& arr)->std::array<int, 2>::size()>()’
    6 |     a<arr.size()>(); // Doesn't
      |     ~~~~~~~~~~~~~^~
test.cpp:3:37: note: candidate: ‘template<long unsigned int s> void a()’
    3 | template <long unsigned int s> void a() {}
      |                                     ^
test.cpp:3:37: note:   template argument deduction/substitution failed:
test.cpp:6:18: error: ‘arr’ is not a constant expression
    6 |     a<arr.size()>(); // Doesn't
      |     ~~~~~~~~~~~~~^~
test.cpp:6:15: note: in template argument for type ‘long unsigned int’
    6 |     a<arr.size()>(); // Doesn't
      |       ~~~~~~~~^~

I assume the ‘arr’ is not a constant expression part is most relevant, but I don't understand why the same line works in main() (is it a constant expression there?), and why passing arr as a const copy (rather than a reference) also resolves the issue.

PS: I know that I can just use a<s>();, but I'm just trying to figure out what this error means.


Solution

    1. Maybe in C++2X, it will work with consteval functitons.

    2. For non-reference argument it works https://godbolt.org/z/Wx9va54z5. I think it is fine to pass std::array of built-ins by value in general anyway. (This works in GCC and clang.)

    #include <array>
    
    template <long unsigned int s> void a() {}
    
    template <long unsigned int s> void b(std::array<int, s> arr) {
        a<arr.size()>(); // ok
    }
    
    int main() {
        const std::array<int, 2> arr = {};
        a<arr.size()>(); // Works
        b<arr.size()>(arr);
        return 0;
    }
    
    1. To answer the question in your comment @acumandr, yes, a static and constexpr size() member function in std::array would work(!), even for const& argument. https://godbolt.org/z/anP1e9qEr

    CORRECTION: This is correct only for GCC, in clang doesn't work https://godbolt.org/z/65d5G9Yfo , Thanks @IlCapitano

    #include <array>
    
    template<class T, std::size_t D>
    struct MyArray{
        static constexpr std::size_t size(){return D;}
    };
    
    template <long unsigned int s> void a() {}
    
    template <long unsigned int s> void b(MyArray<int, s> const& arr) {
        a<arr.size()>(); // ok
    }
    
    int main() {
        const MyArray<int, 2> arr = {};
        a<arr.size()>(); // Works
        b<arr.size()>(arr);
        return 0;
    }
    

    which makes even more puzzling why std::array doesn't have a static (and constexpr) size member.

    2.5) CORRECTION: This is correct only for GCC, in clang doesn't work https://godbolt.org/z/65d5G9Yfo , Thanks @IlCapitano

    In clang, a static funciton works but not passing the instance, which kind of defeats the purpose:

    https://godbolt.org/z/En13aEWPz

    #include <array>
    
    template<class T, std::size_t D>
    struct MyArray{
        static constexpr std::size_t size(){return D;}
    };
    
    template <long unsigned int s> void a() {}
    
    template <long unsigned int s> void b(MyArray<int, s> const& arr) {
        a<std::decay_t<decltype(arr)>::size()>(); // error: no matching function for call to 'a'
    }
    
    int main() {
        const MyArray<int, 2> arr = {};
        a<arr.size()>(); // Works
        b<arr.size()>(arr);
        return 0;
    }
    
    1. using std::tuple_size<decltype(...)> (or s itself) is a good workaround https://godbolt.org/z/K9xxKa1Px
    #include <array>
    
    template <long unsigned int s> void a() {}
    
    template <long unsigned int s> void b(std::array<int, s> arr) {
        a<std::tuple_size<decltype(arr)>::value /*or just s*/>(); // ok
    }
    
    int main() {
        const std::array<int, 2> arr = {};
        a<arr.size()>(); // Works
        b<arr.size()>(arr);
        return 0;
    }