Reputation: 301
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
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
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
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