6

I have a template function that I want to enable only for standard containers (or containers compatible with standard containers, which at least provide a begin() member function). I'm SFINAE-ing out non-containers in the following way:

template<typename Container>
typename Container::value_type 
f(const Container& c,
    typename std::enable_if<
        std::is_same<
            decltype(*c.begin()),
            typename Container::value_type
        >::value
    >::type* = nullptr)
{
    // implementation here
}

The std::is_same and decltype don't look too elegant. Is there any better way of doing this?

PS: I need the SFINAE here because I have a different overload

template<typename Derived>
f(const Eigen::MatrixBase<Derived>& A)

and whenever I try f(some_Eigen_matrix), the Container overload ends up being picked up, then the compiler spits out an error because the type is lacking begin().

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 3
    Why are you doing the equality check, anyway? Doesn't it suffice if the expression is valid? – Columbo May 14 '15 at 18:16
  • `auto f(const Container &c) -> decltype(c.begin(), (void) c.end())` – T.C. May 14 '15 at 18:17
  • Ok, it's a bit more messy. The return type is a sum over the elements of the container. – vsoftco May 14 '15 at 18:19
  • Might make more sense to write a trait to see if something derives from `MatrixBase`, and then tag dispatch on that. – T.C. May 14 '15 at 18:21
  • @T.C. that's almost impossible, since `MatrixBase` is in general an expression template, so I cannot use the comma operator with different types. – vsoftco May 14 '15 at 18:22
  • I'm trying to understand your objections...and failing miserably. Care to elaborate? – T.C. May 14 '15 at 18:28
  • @T.C. `template Eigen::MatrixBase< Derived >` is a template base class. In general, an expression `A+B` is of some type `Eigen::MatrixBase`, whereas something like `A*B` is of type `Eigen::MatrixBase` (and this generalizes when we have more complicated things like e.g. `A+B*C`). There is no relationship between these `MatrixBase` instantiations. – vsoftco May 14 '15 at 18:29
  • @vsoftco But you can check that it is a `MatrixBase` for some `T`, not a *specific* `T` – Barry May 14 '15 at 18:35
  • @Barry How do you do that? Will `is_same` work with generic `T`? – vsoftco May 14 '15 at 18:35
  • @vsoftco `template is_matrix : false_type { }; template is_matrix> : true_type { };` ? – Barry May 14 '15 at 18:40
  • @Barry thanks, I have to look more into templates. Of course it works :) – vsoftco May 14 '15 at 18:43
  • @vsoftco http://stackoverflow.com/questions/30202552/template-method-matching-derived-type-instead-of-base/30203283#30203283 – T.C. May 14 '15 at 18:56

1 Answers1

7

Using void_t, we can just make a type trait for having begin() and end() (and anything else you might want to check for, like typename T::iterator, you can just keep piling expressions on):

template <typename T, typename = void>
struct is_std_container : std::false_type { };

template <typename T>
struct is_std_container<T,
    void_t<decltype(std::declval<T&>().begin()),
           decltype(std::declval<T&>().end()),
           typename T::value_type
           >>
    : std::true_type { };

And then just SFINAE on that:

template <typename Container>
typename std::enable_if<
    is_std_container<Container>::value,
    typename Container::value_type
>::type 
f(const Container& c) { .. }

Also, if you really wanted to verify that begin() gives you back a T::iterator (or at least that they're equality comparable), you can do that too:

void_t<
    decltype(begin(std::declval<T&>()) == std::declval<typename T::iterator>())
>
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Yes, this indeed looks better, and I'll probably go this way, since I am re-using this check. Thanks! – vsoftco May 14 '15 at 18:23
  • The `void_t` trick seems very neat, thanks for the link to the paper. – vsoftco May 14 '15 at 18:40
  • 1
    The only thing it escapes me is why the `is_std_container` will pick up the `true_type` specialization when `Container` is indeed a container? Sorry if the question seem silly. I actually found http://stackoverflow.com/questions/27687389/how-does-void-t-work, will read it. I think the idea is that a matchin partial specialization is always better. CLEVER! – vsoftco May 14 '15 at 18:47