4

I wrote an abstract container template class that should define numeric operators (unary + and -, binary +, - and *) if it make sens for the template parameter (that is, if it is a numeric type).

Then, I would like to apply those numeric operations on containers of containers of numeric values (and on containers of containers of containers of numeric values, and so on).

I wrote the following code. The (A) marker shows how I tried to solve the recursive specialization problem.

template <typename T>
struct is_numeric : public std::is_arithmetic<T>{};

template <typename T> /* (A) */
struct is_numeric<GenericContainer<T>> : public std::is_arithmetic<T>{};


/* Classic generic container for non-numeric base types */
template <typename T, bool isNumeric=false>
class BaseContainer : public GenericContainer<T> {};

/* Numeric container: +,-,* operations for numeric base types */
template <typename T>
class BaseContainer<T, true> : public NumericContainer<T> {};

/* Arithmetic base types should map on numeric containers */
template <typename T>
class Container : public BaseContainer<T, is_numeric<T>::value> {};

Then, in a test program, I have the following assertions:

/* Vector inherits from Container */
typedef Vector<int, 3> V3D;
ASSERT(is_numeric<int>::value);    /* # => OK */
ASSERT(is_numeric<double>::value); /* # => OK */
ASSERT(is_numeric<V3D>::value);    /* # => FAIL */

The two firsts assertions work as expected

Titou
  • 63
  • 5

4 Answers4

1

Did you try :

template <typename T>
struct is_numeric : public std::is_arithmetic<T>{};

template <template<class...> class Container, typename T, typename... Rest>
struct is_numeric<Container<T, Rest...>> : public is_numeric<T>{};

Seems to work for me.

Drax
  • 12,682
  • 7
  • 45
  • 85
  • It won't work because then all the instantiations of `is_numeric` will have to be with template types; i.e., it won't work with `int`. – FireAphis Dec 30 '13 at 15:38
  • @FireAphis Of course i meant in addition to the base definition :) [Post Edited] – Drax Dec 30 '13 at 15:41
  • While this solution seems elegant and straightforward, `ASSERT(is_numeric>::value);` still doesn't work =/ – Titou Dec 30 '13 at 16:51
  • @Titou I edited the post, it should work now :) It will always take the first template parameter of your container into consideration and drop the rest, if you want another behavior it should be adaptable :) – Drax Dec 30 '13 at 16:59
1

Boost's enable_if and type traits allow tricks like you need:

template <class T, class Enable = void> 
struct is_numeric : public std::is_arithmetic<T> {};

template <class T>
struct is_numeric<T, typename enable_if<is_base_of<GenericContainer<T>, T> >::type>
            : public std::is_arithmetic<T> {};

The solution employs SFINAE principle to compile the second version of is_numeric when the template parameter meets the criteria inside enable_if. Notice that the syntax of is_base_of is is_base_of<Base, Derived>. There is more explanation in Boost's enable_if documentation.

Since the relationships in your case are even more complicated, as David Rodriguez kindly mentioned, you should probably make it a bit differently:

template <template <class> class U, class T>
struct is_numeric<U<T>, typename enable_if<is_base_of<GenericContainer<T>, U<T> > >::type>
            : public std::is_arithmetic<T> {};

And if you cannot use the libraries themselves, you can always use them as inspiration :)

FireAphis
  • 6,650
  • 8
  • 42
  • 63
  • 1
    While the idea of using SFINAE is good, the actual details are wrong. You don't want to check whether `GenericContainer` is a base of `T`, but rather whether it `GenericContainer` for a nested type `U` held by `T` is a base of `T`. That is, `Vector` is most probably not derived from `GenericContainer >`. Additionally, the inheritance should be from `is_numeric`, so that the same trait can be used recursively to peel off the different container layers (for example for `Vector,3>`). Still +1 for suggesting SFINAE – David Rodríguez - dribeas Dec 30 '13 at 15:39
  • @DavidRodríguez-dribeas, you are right. I just wanted to show the general idea. I'll expand the answer... – FireAphis Dec 30 '13 at 16:16
1

Your solution fails for a very specific reason: a template type parameter specialization will match only the exact type, and not any derived type.

If you wish for derived types to also match, you need switch gears and use another strategy. In the age of constexpr switching to functions will let you use overloading resolution to your advantage (as one strategy among others):

// Basis
constexpr bool is_numeric_impl(...) { return false; }

template <typename T>
constexpr bool is_numeric(T const& t) { return is_numeric_impl(&t); }

// Specializations
template <typename T,
          typename = std::enable_if<std::is_arithmetic<T>::value>::type>
constexpr bool is_numeric_impl(T const*) { return true; }

template <typename T>
constexpr bool is_numeric_impl(GenericContainer<T> const*) {
    return is_numeric((T const*)nullptr);
}

The main benefit being that this solution is open-ended so that other people may reuse the same traits and add specializations; because it uses a white-list.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

You need to define the is_numeric trait for each container, you cannot just use the base definition.

template <typename T, size_t N>
struct is_numeric< Vector<T,N> > : is_numeric< GenericContainer<T> > // *
{};

Also note that the definition of the is_numeric should be similar the one in the comment, not the one in the question. That is, you want to define is_numeric for a container in terms of whether the nested type is numeric or not (so that you can peel off the different layers).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Therefore, should I specialize `is_numeric` for each particular template specialization (such as `V3D`); or only for final templates classes (such as `Vector`) ? Is there a nicer solution for my problem (that might involve refactoring existing class templates) ? – Titou Dec 30 '13 at 15:33
  • @Titou: Specializing for `Vector` should suffice. You could also use helpers to force checking the base types, this might lead to cleaner code to look at, but it might be trickier to get to the correct solution. – David Rodríguez - dribeas Dec 30 '13 at 15:35