1

I have two functions

template <class T> void foo(std::vector<T>* p)

and

template <class T> void foo(std::valarray<T>* p)

The code in the function bodies is identical. Is there a clever way of writing the template so I can avoid the duplication? Something like

template <class T, class Container> void foo(Container<T>* p)

I'm using C++14.

I only want to allow Container to be either std::vector or std::valarray.

P45 Imminent
  • 8,319
  • 4
  • 35
  • 78

2 Answers2

2

You can also create type traits only for your case.

template <typename T>
struct is_vector_or_valarray : std::false_type {};

template<class T>
struct is_vector_or_valarray<std::vector<T>> : std::true_type {};

template<class T>
struct is_vector_or_valarray<std::valarray<T>> : std::true_type {};

template<typename C, typename = std::enable_if_t<is_vector_or_valarray<C>::value>>
void foo(C* p) {}

Demo


If you want to allow classes derived from std::vector and std::valarray passed in.

template<template<typename...> typename T, typename U>
struct is_tbase_of
{
private:
    template<class V>
    static std::pair<V, decltype(static_cast<const T<V>&>(std::declval<U>()), std::true_type{})> test(const T<V>&);
    static std::pair<U, std::false_type> test(...);
public:
    using _aux_type = decltype(test(std::declval<U>()));
    using base_type = T<typename _aux_type::first_type>;
    static constexpr bool value = _aux_type::second_type::value;
};


template<typename C,
         typename = std::enable_if_t<
                        is_tbase_of<std::vector, C>::value
                        || is_tbase_of<std::valarray, C>::value>>
void foo(C* p) {
    using base_t = std::conditional_t<
                    is_tbase_of<std::vector, C>::value,
                    typename is_tbase_of<std::vector, C>::base_type,
                    typename is_tbase_of<std::valarray, C>::base_type>;
    auto pbase = static_cast<base_t*>(p);

    // operate on pbase
}

The type traits for checking if a class inherits from a template class comes from this answer. I make some subtle changes based on it(mainly for getting the base type).

As @Raymond says, c should be cast to its base pointer to operate on the sliced part. I believe it should be done in the function body, otherwise, there'll be a non-deduced context.

Demo

Nimrod
  • 2,908
  • 9
  • 20
  • Fails for `struct S:std::vector{} s; foo(&s);` You probably have to `enable_if` on `is_convertible` (and then cast to `std::vector*` to ensure you operate on the slice, like the original function did). Better would be to `enable_if` on some property of `vector` and `valarray` that the underlying algorithm relies on (e.g. having a `value_type` and a subscript operator). – Raymond Chen Jun 29 '22 at 04:58
  • @RaymondChen Thanks for pointing it out. Wil something like `std::is_base_of` be enough in such case rather than `std::is_convertible`? I'll try to fix it when I have time a bit later. – Nimrod Jun 29 '22 at 08:06
  • I updated my answer at least to meet the derived class pointer requirement, I believe. – Nimrod Jun 29 '22 at 14:27
1

Note: you cannot really specialize on std::vector<> and std::valarray<>, but you can say, for the given (deduced) T, you expect std::vector<T> or std::valarray<T>. Thus aliases will also be resolved.

The trick is, you templetize for a template class C and a type T (and possibly further parameters, as in std), then use std::enable_if<> that only resolves (to void in this case) when either std::vector<T> or std::valarray<T> was resolved.

Also note, you might want to allow custom allocators et. al., i.e., instead of a single T, it's recommended to use typename... Ts for the container.

#include <iostream>
#include <vector>
#include <valarray>
#include <type_traits>

template<template<class...> class C, typename T>
auto foo(C<T>* arg) -> std::enable_if_t<std::is_same<C<T>, std::vector<T>>::value || std::is_same<C<T>, std::valarray<T>>::value>
{
}

int main() {
    std::vector<int> v;
    std::valarray<int> va;
    //std::pair<int, int> p; // this should fail
    foo(&v);
    foo(&va);
    //foo(&p); // this fails as expected
    return 0;
}

If you also want to handle derived classes, you might use std::is_base_of<> - but as for classes in std namespace, it's not recommended to derive from them.

lorro
  • 10,687
  • 23
  • 36
  • Fails for `template S:std::vector{}; S s; foo(&s);` – Raymond Chen Jun 29 '22 at 13:27
  • @RaymondChen Not sure what you mean by `template S:std::vector{};`. If you mean `template using S = std::vector;`, then it works as expected. – lorro Jun 30 '22 at 13:33
  • Sorry. Left out a word. `template struct S : std::vector {};` The point is that the `enable_if` is not matching pointers to derived types, but the original functions did. – Raymond Chen Jun 30 '22 at 13:38
  • @RaymondChen Indeed, but that's a feature, not a bug :). You could, of course match for it - `std::is_base_of<>` -, but the generic rule is not to inherit from std's containers (composition over inheritance). – lorro Jun 30 '22 at 13:41
  • It may not be advisable in this situation, but somebody might stumble across this question in another context where derivation is acceptable and try to adapt the solution. (And it should nevertheless be called out as "This isn't exactly the same as the two-overload version, but the places where it's different aren't places you should be going.") – Raymond Chen Jun 30 '22 at 14:40
  • @RaymondChen Added a sentence to cover this. – lorro Jun 30 '22 at 14:43