Ryan McCampbell
Ryan McCampbell

Reputation: 301

Template overload precedence

I want to have two overloads of a template function but have one take precedence. I am trying to define a size() function that uses the size member function if available but falls back to using std::begin() and std::end() (This is needed for say std::forward_list()). This is what they look like:

template <class Container>
constexpr auto size(const Container& cont) -> decltype (cont.size())
{
    return cont.size();
}

template <class Container>
auto size(const Container& cont) -> decltype (
    std::distance(std::begin(cont), std::end(cont)))
{
    return std::distance(std::begin(cont), std::end(cont));
}

The problem is that the compiler can't decide which overload to use for containers with a size() and a begin()/end(). How do I make it choose the first implementation when possible? (I know SFINAE is part of the solution, but I am not knowledgeable enough in the arcane arts to figure it out)

Also (unrelated), is there an easier way to declare the return type for the second function?

Upvotes: 2

Views: 108

Answers (3)

T.C.
T.C.

Reputation: 137301

Yet another one, with bog-standard overload resolution as the tiebreaker:

namespace details {
    template <class Container>
    constexpr auto size(const Container& cont, int) -> decltype (cont.size())
    {
        return cont.size();
    }

    template <class Container>
    auto size(const Container& cont, ...) -> decltype (
        std::distance(std::begin(cont), std::end(cont)))
    {
        return std::distance(std::begin(cont), std::end(cont));
    }
}

template <class Container>
auto size(const Container& cont) -> decltype(details::size(cont, 0)) {
    return details::size(cont, 0);
}

Upvotes: 1

Brandon
Brandon

Reputation: 752

Another answer using tag dispatch instead:

#include <vector>
#include <forward_list>
#include <iostream>
#include <cstdint>

template <typename Container>
std::true_type size_type(Container&& c, decltype(c.size()));

template <typename Container>
std::false_type size_type(Container&& c, ...);

template <typename Container>
auto size(Container&& c, std::true_type) {
    return c.size();
}

template <typename Container>
auto size(Container&& c, std::false_type) {
    using std::begin;
    using std::end;
    return std::distance(begin(c), end(c));
}

template <typename Container>
auto size(Container&& c) {
    using type = decltype(size_type(std::forward<Container>(c), 0));
    return size(std::forward<Container>(c), type {});
}

template <typename T, size_t N>
constexpr size_t size(T const (&)[N]) {
    return N;
}

int main() {
    int const arr[10] {};
    std::forward_list<int> const list {1, 3, 5};
    std::vector<int> const v {2, 4};

    std::cout << size(arr) << ", " << size(list) << ", " << size(v);
}

Upvotes: 0

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153792

You'd create a traits specifying if there is a size() function. You then use std::enable_if with that trait and selectively enable the function you prefer to be chosen, disabling the respective other.

This should do the trick:

struct has_size_aux {
    template <typename S>
    static char (&test(decltype(std::declval<S>().size())*))[1];
    template <typename S>
    static char (&test(...))[2];
};

template <typename T>
struct has_size
    : std::integral_constant<bool, 1 == sizeof(has_size_aux::test<T>(nullptr))> {
};

template <class Container, typename = std::enable_if_t<has_size<Container>::value>>
constexpr auto size(const Container& cont)
    -> decltype (cont.size())
{
    return cont.size();
}

template <class Container, typename = std::enable_if_t<!has_size<Container>::value>>
auto size(const Container& cont) ->
    decltype (std::distance(std::begin(cont), std::end(cont)))
{
    return std::distance(std::begin(cont), std::end(cont));
}

Upvotes: 0

Related Questions