Mordachai
Mordachai

Reputation: 9642

Is there a way to force this ambiguity away?

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?

Upvotes: 0

Views: 82

Answers (3)

Richard Hodges
Richard Hodges

Reputation: 69874

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;
}

Upvotes: 1

wally
wally

Reputation: 11002

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
   }

Upvotes: 1

max66
max66

Reputation: 66200

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.

Upvotes: 2

Related Questions