0

I'd like to supply two forms of a GetLength(psz) style function - one that doesn't know an upper bounds, and one that does:

template <typename T>
size_t GetLength(const T * psz) { /* compute size w/o knowing what upper bound may be */ }

template <typename T, size_t size>
size_t GetLength(const T(&psz)[size]) { /* we know the upper bound */ }

I'd like that this not be ambiguous. I want the array-sized version to be chosen when the argument is an array of known size. I want the unbounded version chosen when the argument is just a pointer, not a known fixed array.

I'd also offer a 3rd version which explicitly takes the upper bounds as an argument, without templated size deduction, for passing that info in from an outer context which has otherwise lost the ability to deduce that from its local arguments.

Is there a technique I can use to force the compiler to discount the 1st version of my function (no known bounds) when the bounds is known?

Mordachai
  • 9,412
  • 6
  • 60
  • 112

3 Answers3

2

Is there a technique I can use to force the compiler to discount the 1st version of my function (no known bounds) when the bounds is known?

What about adding a level of indirection?

template <typename T>
std::size_t GetLength (const T * psz, int)
 { /* compute size w/o knowing what upper bound may be */ }

template <typename T, size_t size>
std::size_t GetLength (const T(&psz)[size], long)
 { /* we know the upper bound */ }

template <typename T>
std::size_t GetLength (T const & t)
 { GetLength(t, 0L); }

Adding an unused different parameter (int or long) you can select the preferred version.

max66
  • 65,235
  • 10
  • 71
  • 111
  • This works because 0L will bind to a long in preference to an int? This will work in the specific case of selecting one of two overload categories, but in the general case we'll run out of types :) – Richard Hodges May 09 '19 at 00:23
1

We could use type traits:

#include <type_traits>

   // If T is an array 
   template<
       typename T,
       typename std::enable_if <
       std::is_array<T>{},
       size_t
       > ::type Extent = std::extent<T>::value
   >
   size_t GetLength(const T& t)
   {
       return Extent;
   }

   // If T is not an array 
   template<typename T,
       typename std::enable_if <
       !std::is_array<T>{},
       size_t
       > ::type = 0
   >
   size_t GetLength(const T& t)
   {
       return {};
   }

   int main()
   {
       int arr[5]{};
       GetLength(arr); // calls first

       //decay to pointer
       auto val = arr;
       GetLength(val); // calls second
   }
wally
  • 10,717
  • 5
  • 39
  • 72
1

If you have access to a recent version of boost, you can use the incredibly powerful HOF library (stands for higher order functions).

One of the functions I use most to simplify code path selection based on argument type is the function first_of.

The way this works is that you give it a list of template function objects (or lambdas) in the order you want the compiler to try them. The first legal function object in the list is selected.

example:

#include <cstddef>
#include <boost/hof.hpp>
#include <cstring>
#include <utility>
#include <iostream>

// a function to compute length from a pointer. For exposition, 
// I have only considered char pointers but any number of overloads will work.
template<class T> 
std::size_t 
string_pointer_length(T*p)
{
    // for exposition
    return std::strlen(p);
}

// a function to compute string length from a literal
template<class T, std::size_t N> 
constexpr 
std::size_t literal_string_length(T (&s)[N])
{
    return N - 1;
}

// The generic GetLength function which takes any kind of string
template <typename T>
std::size_t GetLength(T&& str) 
{ 
    // select the FIRST legal choice of the following lambdas and invoke...
    return boost::hof::first_of(
        [](auto&&s) BOOST_HOF_RETURNS(literal_string_length(s)),
        [](auto&&s) BOOST_HOF_RETURNS(string_pointer_length(s))
    )(str);
}


int main()
{
    static const auto lit = "hello";
    auto plit = std::addressof(lit[0]);

    auto n = GetLength(lit);
    auto n2 = GetLength(plit);

    std::cout << n << ", " << n2 << std::endl;
}

The macro BOOST_HOF_RETURNS saves us having to spell out the lambdas like this:

    return boost::hof::first_of(
        [](auto&&s) -> decltype(literal_string_length(s)) { return literal_string_length(s); },
        [](auto&&s) BOOST_HOF_RETURNS(string_pointer_length(s))
    )(str);

If you're not able to use boost.hof, writing our own replacement is surprisingly trivial:

#include <cstddef>
#include <cstring>
#include <tuple>
#include <utility>
#include <iostream>

template<class T> 
std::size_t 
string_pointer_length(T*p)
{
    // for exposition
    return std::strlen(p);
}

template<class T, std::size_t N> 
constexpr 
std::size_t literal_string_length(T (&s)[N])
{
    return N - 1;
}

template<class...Args, class This, class...Others>
constexpr auto try_these(std::tuple<Args...> args, This _this, Others...others)
{
    if constexpr (std::is_invocable_v<This, Args...>)
    {
        return std::apply(_this, args);
    }
    else
    {
        return try_these(args, others...);
    }
}

struct invoke_string_pointer_length
{
    template<class S>
    constexpr auto operator()(S&& s) const -> decltype(string_pointer_length(s)) 
    { return string_pointer_length(s); }
};

struct invoke_literal_string_length
{
    template<class S>
    constexpr auto operator()(S&& s) const -> decltype(literal_string_length(s)) 
    { return literal_string_length(s); }
};

template <typename T>
std::size_t GetLength(T&& str) 
{ 
    return try_these(std::forward_as_tuple(std::forward<T>(str)), 
        invoke_literal_string_length(), 
        invoke_string_pointer_length());
}


int main()
{
    static const auto lit = "hello";
    auto plit = std::addressof(lit[0]);

    auto n = GetLength(lit);
    auto n2 = GetLength(plit);

    std::cout << n << ", " << n2 << std::endl;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thank you! This is extremely cool. I've got loads of contexts where I'm struggling to add a "rank" system to enforce a ranking of overloads, essentially. I can't use this in this particular context of choose_literal, but I can use this elsewhere I'm certain. – Mordachai May 08 '19 at 23:10
  • If you adopt my string_view version of choose_literal (see other question), you won't need it anyway. string_view has a `.size()` method. I'll update the other answer. – Richard Hodges May 08 '19 at 23:16
  • `string_view` is very cool - and long overdue. OTOH, I have reams of legacy code that is performant and is expressed using char_type arrays and interfaces. This code is explicitly to support such legacy code. New code, or refactored code, definitely should use `string_view`. – Mordachai May 08 '19 at 23:21
  • @Mordachai do you mean that you can't use it because you don't have access to boost, or because the actual problem domain is more complex? – Richard Hodges May 08 '19 at 23:21
  • 1
    @Mordachai I see. `std::string_view` (or `boost::string_view`) is `constexpr`-enabled. It performs exactly as fast as a literal char array. – Richard Hodges May 08 '19 at 23:23
  • 1
    @Mordachai updated with non-boost version if that helps. – Richard Hodges May 08 '19 at 23:34