0

I would like to have separate specializations for a function when the type is a container vs. non-container types. For example:

template <typename T>
void print(const T& t) {
  cout << "non-container: " << t;
}

template <>
void print(const ContainerType& container) {
  cout << "container: ";
  for (const auto& t : container) {
    cout << t;
  }
}

Then I could use these functions like:

print(3); // Prints non-container: 3
print(vector{1, 1, 2}); // Prints container: 1 1 3
print(set{1, 1, 2}); // Prints container: 1 2

I don't know how to specify the specialization of print so that it will get called for vector and set but not for int. I don't want to have to create separate specializations for every container type I want a single specialization for any iterable type.

Benjy Kessler
  • 7,356
  • 6
  • 41
  • 69
  • 1
    You can use something like `is_container` from [this answer](https://stackoverflow.com/a/9407521/2296458) in combination with `std::enable_if` machinery to check for container types – Cory Kramer Dec 15 '20 at 13:52
  • 1
    @CoryKramer: or create a concept from that in C++20 :-) – Jarod42 Dec 15 '20 at 14:10

1 Answers1

4

A quick version might be

struct low_priority_tag {};
struct high_priority_tag : low_priority_tag {};

template <typename T>
std::false_type is_container_impl(const T& t, low_priority_tag);

template <typename T>
auto is_container_impl(const T& container, high_priority_tag)
-> decltype(std::begin(container), std::end(container), std::true_type{});

template <typename T>
using is_container = decltype(is_container_impl(std::declval<T>(), high_priority_tag{}));

template <typename T>
void print(const T& t) {
    if constexpr (is_container<T>::value) {
        std::cout << "container: {";
        const char* sep = "";
        for (const auto& e : t) {
            std::cout << sep;
            print(e);
            sep = ", ";
        }
        std::cout << "}";
    } else {
        std::cout << t;   
    }
}

Demo

in C++20, with concept:

template <typename T>
concept Container = requires(T t)
{
    std::begin(t);
    std::end(t);
};

template <typename T>
void print(const T& t) {
    std::cout << t;   
}

template <Container C>
void print(const C& c) {
    std::cout << "container: {";
    const char* sep = "";
    for (const auto& e : c) {
        std::cout << sep;
        print(e);
        sep = ", ";
    }
    std::cout << "}";
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • The `concept` concept looks really cool. It's amazing how much template gibberish is required in C++17. – Benjy Kessler Dec 15 '20 at 16:59
  • @BenjyKessler: We can be a little less verbose by using `int` and `...` as tie breaker instead of explicit priority tag, but it is still too much compared to C++20. – Jarod42 Dec 15 '20 at 17:02