3
votes

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

3
I'm not sure what typename std::enable_if<...>::type = 0 is even supposed to mean as a template parameter declaration. What happens if you use the usual typename = std::enable_if<...>::type syntax?Daniel Schepler
Daniel Schepler, I taken this syntax from 4th example: cppreference. But the problem not in syntax. It will not compile even with typename = std::enable_if<true, int>::type because, as error says: default template arguments may not be used in partial specializations.Vladislav
Hmm, the example referred to is actually using typename std::enable_if<cond, int>::type = 0 which does make sense, even if it's a bit roundabout.Daniel Schepler
Oops, you are right. I missed out int after comma. Honestly, I also missed ::value for is_base_of.Vladislav
@DanielSchepler std::enable_if_t<c, int> = 0 is less typing and supports overloading on mutually exclusive conditions, whereas template <typename = std::enable_if_t<c>> void foo(); template <typename = std::enable_if_t<!c>> void foo(); is ill-formed, two declarations of the same template with different defaults.Oktalist

3 Answers

3
votes

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");
2
votes

What about a good old static_assert() ?

#include <array>
#include <iostream>
#include <type_traits>

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> {};

template <typename>
class tuple_size;


template <template <typename, std::size_t> class T,
          typename TArg,
          std::size_t NArg>
class tuple_size<T<TArg, NArg>>
 {
   static_assert(std::is_base_of<Foo<TArg, NArg>, T<TArg, NArg>>::value,
                 "bad tuple_size class");
 };


int main()
 {
   tuple_size<Foo<int, 12>>          ts1;   // compile
   tuple_size<FooDerived<long, 21>>  ts2;   // compile
   //tuple_size<Bar<int, 11>>          ts3;   // error!
   //tuple_size<BarDerived<long, 22>>  ts4;   // error!
 }
0
votes

If you want different tuple_sizes classes for different derived groups types, my precedent solution (based on static_assert()) does't work.

I propose the following solution based on std::conditional and std::is_base_of

The tricky (and ugly, I suppose) part is the struct baseType that, given a type, detect a base (between Foo and Bar), if possible.

#include <array>
#include <iostream>
#include <type_traits>

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> {};


template <typename>
struct baseType;

template <template <typename, std::size_t> class T,
          typename TArg,
          std::size_t NArg>
struct baseType<T<TArg, NArg>>
 {
   using derT = T<TArg, NArg>;
   using bas1 = Foo<TArg, NArg>;
   using bas2 = Bar<TArg, NArg>;
   using type = typename std::conditional<
                   std::is_base_of<bas1, derT>::value, bas1,
                      typename std::conditional<
                         std::is_base_of<bas2, derT>::value, bas2,
                            void>::type>::type;
 };

template <typename T, typename = typename baseType<T>::type>
class tuple_size;

template <template <typename, std::size_t> class T,
          typename TArg,
          std::size_t NArg>
class tuple_size<T<TArg, NArg>, Foo<TArg, NArg>>
 { public: static constexpr std::size_t size {NArg}; };

template <template <typename, std::size_t> class T,
          typename TArg,
          std::size_t NArg>
class tuple_size<T<TArg, NArg>, Bar<TArg, NArg>>
 { public: static constexpr std::size_t size { NArg << 1 }; };

int main()
 {
   std::cout << "size of Foo<int, 12>:         "
      << tuple_size<Foo<int, 12>>::size << std::endl; //         print 12
   std::cout << "size of FooDerived<long, 11>: "
      << tuple_size<FooDerived<long, 11>>::size << std::endl; // print 11
   std::cout << "size of Bar<int, 12>:         "
      << tuple_size<Bar<int, 12>>::size << std::endl; //         print 24
   std::cout << "size of BarDerived<long, 11>: "
      << tuple_size<BarDerived<long, 11>>::size << std::endl; // print 22
   //std::cout << "size of std::array<long, 10>: "              // compiler 
   //   << tuple_size<std::array<long, 10>>::size << std::endl; // error
 }