2

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?

Ryan McCampbell
  • 301
  • 1
  • 2
  • 8
  • Your going to need something along the lines of [this](http://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature). – NathanOliver Jun 20 '16 at 23:47

3 Answers3

1

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);
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Before my current answer, my [initial attempt](http://coliru.stacked-crooked.com/a/1edb89ad164d256c) was along this kind of usual line, but it fails. I don't exactly get why the `template argument is not a constant expression` though yours is admittedly, more simpler than my initial attempt. – WhiZTiM Jun 21 '16 at 08:38
0

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));
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
0

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);
}
Brandon
  • 724
  • 5
  • 12