8

I have this code:

template <typename T, ::std::size_t size>
using ary_t = T[size];

template <typename T, ::std::size_t size>
constexpr int call_me(ary_t<T const, size> &a)
{
    int total = 10;
    for (::std::size_t i = 0; i < size; ++i) {
        total += a[i];
    }
    return total;
}

template <typename T>
constexpr int call_me(T const *a)
{
    int total = 0;
    for (int i = 0; a[i]; ++i) {
        total += a[i];
    }
    return total;
}

#if 0
int t1()
{
    return call_me("a test");
}
#endif

int t2()
{
    char const * const s = "a test";
    return call_me(s);
}

and it works, but when remove the #if 0 section around t1 it fails to compile because of an ambiguity in which template to use. Is there any way to force the array version of call_me to be used preferentially?

I've tried a number of different tricks to make this work. I've tried adding , int... to the template argument list for the pointer version. I've tried removing the const. I've tried both. I've even tried making the pointer version into a C-style varargs function (aka int call_me(T const *a, ...)). Nothing seems to work.

I'd be happy with an answer that requires what is currently believed will make it into C++2a.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 1
    Yes! as I hypothesized. Thats not an array. You can't just T[size], that's a pointer in disguise. You have to T(&)[size]. Aside from your error, T(&)[size] is still ambiguous with T*. Cpp is strange. Similar to how 0 and nullptr is ambiguous where function accepts int or pointer. – user13947194 Mar 08 '23 at 01:20
  • @user13947194 - Oh, interesting. C++ declarations are very confusing sometimes. – Omnifarious Aug 21 '23 at 06:05

3 Answers3

8

There's an easy workaround:

template <typename T>
constexpr int call_me(T&& arg) {
    if constexpr(std::is_pointer_v<std::remove_reference_t<T>>) {
        return call_me_pointer(arg);
    } else {
        return call_me_array(arg);
    }
}
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
3

If you accept to add a level of indirection, you can add an unused parameter to give the precedence to the array version.

I mean

template <typename T, std::size_t size>
constexpr int call_me_helper (ary_t<T, size> &a, int)
{
    int total = 10;
    for (std::size_t i = 0; i < size; ++i) {
        total += a[i];
    }
    return total;
}

template <typename T>
constexpr int call_me_helper (T const * a, long)
{
    int total = 0;
    for (int i = 0; a[i]; ++i) {
        total += a[i];
    }
    return total;
}

template <typename T>
constexpr int call_me (T const & a)
 { return call_me_helper(a, 0); }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    This is also an interesting answer, and it's also a completely C++11 answer, which is nice. I didn't mind using a newer version, but it's nice to keep ones code working for older compilers. It has no more indirection than the top rated answer. I would probably change the wrapper function to take a `&&` and use `::std::forward`. I should test that to make sure it'd work. – Omnifarious Apr 29 '19 at 23:10
  • Well, OK, not C++11 because of the `constexpr`, but yes. – Omnifarious Apr 30 '19 at 00:25
  • 1
    @Omnifarious - Exactly: it's a C++14 solution and, removing `constexpr`, also C++11; if you use `T const (&a)[size]` instead of `ary_t &a` become also C++98. About perfect forwarding... yes, in a more complex example make sense; but, in this case, you don't use move semantics inside the `call_me_helper()` functions so, IMHO, a classic `T const &` is adequate. – max66 Apr 30 '19 at 11:32
  • Oh man! This is a super duper idea. Like, there is no such thing as an rvalue array, so logically all arrays passed to a function will be an lvalue. Note that {...} is a std::initializer_list expression. – user13947194 Mar 08 '23 at 01:47
  • PS. This works for template functions and not template class template functions. The compiler, (gcc I use), still complains ambiguous overload. – user13947194 Mar 08 '23 at 14:43
1

I suggest you achieve the same effect by using a span:

What is a "span" and when should I use one?

You just replace the array reference with a fixed-size span:

#include <cstddef>
#include <gsl/span>

template <typename T, std::size_t size>
constexpr int call_me(gsl::span<T const, size> a)
{
    int total = 10;
    for (std::size_t i = 0; i < size; ++i) {
        total += a[i];
    }
    return total;
}

And there's no ambiguity. What's even nicer is that now you can use standard-library algorithms on containers:

#include <numeric>

template <typename T, std::size_t size>
constexpr int call_me(gsl::span<T const, size> a)
{
    return std::accumulate(a.begin(), a.end(), 10);
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684