0

I'm trying to practice in traits and SFINAE and try to write something like is_container for a vector:

template <typename... Ts>
struct is_container :std::false_type {};

template <typename... Ts>
struct is_container< std::vector<Ts...> >:std::true_type {};

template < typename  T>
inline constexpr auto is_container_v = is_container<T>::value;

And actually such a function:

template<typename T, typename std::enable_if_t<is_container_v<T>, int > = 0>
auto foo(T&& first, T&& second) {
    //do smth...
}

But such a call does not see the function:

std::vector<int> test_vec1(4,0);
std::vector<int> test_vec2(5,3);

foo(test_vec1, test_vec2);

I can't see where the error is.I think that the vector should have 2 parameters:

template<class T, class Alloc> 
struct is_container<std::vector<T, Alloc>>

But shouldn't the variadic template work? or a mistake in another?I will be happy to help

Alpharius
  • 489
  • 5
  • 12

2 Answers2

2

T&& is a universal reference. In this case it is matched as T = std::vector<int>&. Your is_container_v<T> currently returns

is_container_v<decltype(test_vec1)> // true
is_container_v<std::vector<int>>    // true
is_container_v<std::vector<int>&>   // false
is_container_v<std::vector<int>&&>  // false

In order to evaluate to true for references you need to remove the reference at some point either with std::remove_reference_t or even better with std::decay_t

is_container_v<std::remove_reference_t<std::vector<int>&>>  // true
is_container_v<std::remove_reference_t<std::vector<int>&&>> // true

The way you have written is_container it is actually not that simple to include it.

  • You could simply modify your function to handle it

    template<typename T, typename std::enable_if_t<is_container_v<std::decay_t<T>>,int>* = nullptr>
    auto foo(T&&, T&&) {
      return;
    }
    

    This is quick and simple but in this case is_container as well as is_container_v still behave somewhat unexpectedly with references. Try it here!

  • You could change the is_container_v alias to something like

    template <typename T>
    inline constexpr auto is_container_v = is_container<std::decay_t<T>>::value;
    

    This does not fix though the is_container struct itself which still behaves in an unexpected manner.

  • If you want to included it into the is_container struct directly you actually have to rewrite it in one way or the other either by making it a template template class or doing something like this:

    template <typename T1, typename T2 = void>
    struct is_container: std::false_type {};
    
    template <typename T>
    struct is_container<T,
      std::enable_if_t<std::is_same_v<std::decay_t<T>,
        typename std::vector<typename std::decay_t<T>::value_type, typename std::decay_t<T>::allocator_type> >>
    >: std::true_type {};
    

    I added a second template parameter which is defaulted to void. This can then be used to enable and disable certain specialisations with SFINAE and std::enable_if_t. Instead of making it a template template class I extract the value and allocator type (which are the template arguments of an std::vector) and then make sure with std::decay that the decayed data type T is identical to an std::vector with element type T::element_type and allocator T::allocator_type. Try it here!

2b-t
  • 2,414
  • 1
  • 10
  • 18
  • thank you more for your help and explanation! – Alpharius May 22 '21 at 15:11
  • @Alpharius I would use `std::decay`. What I am not sure is where t add it. Of course you could add it to the function itself but wouldn't you expect `is_contaner_v` to be true for references to containers as well? I would think about adding it somewhere in `is_container` already. – 2b-t May 22 '21 at 15:13
  • thank you again, I think it's better to add it to the definition itself – Alpharius May 22 '21 at 15:20
  • I apologize,but maybe you know why here ```struct is_container< std::remove_reference_t> >:std::true_type {}``` returns an error " Ts is not used in or cannot be deduced " ? – Alpharius May 22 '21 at 15:32
  • 1
    @Alpharius No, it can't be done that like this. You can add it in a simple way to `template inline constexpr auto is_container_v = is_container>::value;` but I can't think of a quick fix to `is_container` directly. I don't have time now but I bookmarked this tab and I will have a look at how to rewrite it in a couple of hours. – 2b-t May 22 '21 at 16:14
  • 1
    @Alpharius This is the closest to your syntax that I could come up with: [Wandbox](https://wandbox.org/permlink/cVd9pfFn366jFWek). I have written a post about the template deducation for STL containers some while back: [Read it here](https://stackoverflow.com/a/67170470/9938686). The problem is that it is actually a template itself which takes two template parameters, the value type as well as the allocator. So either you write the specialisation for a `template template` class or you assume it is a class which contains two static class members a `value_type` and `alloc_type`. – 2b-t May 22 '21 at 17:04
  • Unreal thank you for your answer, which helped a lot and your time ! :) – Alpharius May 23 '21 at 09:21
1

You are passing lvalues not rvalues, hence to use your implementation as it's use std::move() or change

auto foo(T&& first, T&& second) 

to

auto foo(T& first, T& second)
asmmo
  • 6,922
  • 1
  • 11
  • 25
  • isn't that a universal reference?why doesn't it work – Alpharius May 22 '21 at 14:59
  • Yeah, isn't `T&&` actually a forwarding reference in this case? I don't see how this could be the problem. – Nathan Pierson May 22 '21 at 15:00
  • 2
    For the universal reference `T&&`, when passed `std::vector&`, `T` is deduced as `std::vector&`, resulting in a declared type of `std::vector& &&`, which collapses to the final type `std::vector&`. See https://stackoverflow.com/q/13725747/21475 – Cameron May 22 '21 at 15:04