1

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.

acumandr
  • 21
  • 4
  • Function arguments can't be used as constant expressions. – cigien May 01 '21 at 21:16
  • @cigien If I change b's signature to `void b(const std::array arr)` (pass by copy), there is no error. Is that not using a function argument as a constant expression? – acumandr May 01 '21 at 21:24
  • Related: https://stackoverflow.com/questions/21936507/why-isnt-stdarraysize-static/35988594#35988594 – alfC May 01 '21 at 21:24
  • Hmm, that shouldn't compile. There's probably a correct dupe somewhere, but the one I used isn't, so I've reopened. – cigien May 01 '21 at 21:32
  • Here's a reasonable [target](https://stackoverflow.com/questions/65236583). It appears that `std::array` passed to a function by *value* can be used as a constant expression, but not if it's passed by *reference*. – cigien May 01 '21 at 21:38
  • @alfC If the size were static, would this work? Thanks for the context though! – acumandr May 01 '21 at 21:55
  • @cigien I can't say I understand why references specifically can't be a constant expression, but I guess that answers my question (even if only to say that that's just what the standard says). Thanks! – acumandr May 01 '21 at 21:56

1 Answers1

1
  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;
}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • 1
    Your second example [doesn't work with Clang](https://godbolt.org/z/93asTGPYT). – IlCapitano May 02 '21 at 01:25
  • @IlCapitano, good catch, thanks. I added a correction. – alfC May 02 '21 at 01:31
  • 1
    I think that the post @cigien linked answered the question as I understood it (I didn't even think about what the size was bound to), but I like this point! (I wish I could upvote!) – acumandr May 02 '21 at 01:49