Search code examples
c++c++11templatessfinaepartial-specialization

Define partial specialization for some external template classes with restriction for template parameters


I have a class Foo and class Bar. They are wrappers over std::array. Both of them have some derived classes.

template<typename T, std::size_t N>
struct Foo {
    std::array<T, N> data;
};

template<typename T, std::size_t N>
struct FooDerived : Foo <T, N> {};

template<typename T, std::size_t N>
struct Bar {
    std::array<T, N> data;
};

template<typename T, std::size_t N>
struct BarDerived : Bar <T, N> {};

And I want to implement tuple-interface in std namespace: get / tuple_size / tuple_element. But these methods should be available only for Foo and derived from Foo classes.

namespace std {
template<template<typename, std::size_t> class T, typename TArg, std::size_t NArg>
class tuple_size<T<TArg, NArg>>
  : public integral_constant<std::size_t, NArg>
  {
  };

template<std::size_t I, template<typename, std::size_t> class T, typename TArg, std::size_t NArg>
struct tuple_element<I, T<TArg, NArg>>
  {
  using type = TArg;
  };
} // namespace std

That works, but works also with Bar and Bar-derived classes.

I thought to use std::enable_if with std::is_base_of. std::enable_if for classes can be used as a template parameters. And if I write:

template<template<typename, std::size_t> class T, typename TArg, std::size_t NArg,
    typename std::enable_if<std::is_base_of<Foo<TArg, NArg>, T<TArg, NArg>>::value, int>::type = 0>
class tuple_size<T<TArg, NArg>>
  : public integral_constant<std::size_t, NArg>
  {
  };

it leads to compile error: default template arguments may not be used in partial specializations.

Is it possible to forbid to use tuple-like interface for non-related to Foo classes?

Example: http://rextester.com/JEXJ27486

Update: Seems I find out the solution. More flexible than just static_assert. If it is impossible to use default template arguments in partial specialization - add additional class with full template specialization.

template<typename T, typename TArg, std::size_t NArg, typename = void>
struct tuple_resolver;

template<typename T, typename TArg, std::size_t NArg>
struct tuple_resolver<T, TArg, NArg,
    typename std::enable_if<std::is_base_of<Foo<TArg, NArg>, T>::value>::type>
    : public integral_constant<std::size_t, NArg>
{
    using type = TArg;
};

template<template<typename, std::size_t> class T,
    typename TArg,
    std::size_t NArg>
class tuple_size<T<TArg, NArg>>
  : public tuple_resolver<T<TArg, NArg>, TArg, NArg>
  {
  };

Example: http://rextester.com/KTDXNJ90374


Solution

  • If you're OK with using proposed but not yet standardized language features, this seems to work under gcc 6.1 with the -fconcepts flag:

    template <typename Base, typename Derived>
    concept bool BaseOf = std::is_base_of<Base, Derived>::value;
    
    namespace std {
      template <template<typename,std::size_t> class Tmpl, typename T, std::size_t N>
      requires BaseOf<Foo<T, N>, Tmpl<T, N>>
        class tuple_size<Tmpl<T, N>>
        : public std::integral_constant<std::size_t, N>
        { };
    };
    
    // tests
    static_assert(std::tuple_size<FooDerived<int, 5>>::value == 5,
                  "FooDerived");
    static_assert(std::tuple_size<std::array<int, 5>>::value == 5,
                  "std::array");
    
    template <typename T>
    concept bool SizedTuple = requires {
      { std::tuple_size<T>::value } -> std::size_t
    };
    static_assert(!SizedTuple<BarDerived<int, 5>>, "BarDerived");