为某些外部模板 类 定义部分特化,并限制模板参数

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

我有一个 class Foo 和 class Bar。它们是 std::array 的包装器。 它们都有一些派生的 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> {};

我想在 std 命名空间中实现元组接口:get / tuple_size / tuple_element。但是这些方法应该 可用于 Foo 并且派生自 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

这有效,但也适用于 Bar 和 Bar 派生的 classes。

我想用 std::enable_if 和 std::is_base_of。 std::enable_if for classes 可以用作模板参数。 如果我写:

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

它会导致编译错误:默认模板参数不能用于偏特化。

是否可以禁止使用与 Foo classes 无关的类似元组的接口?

示例:http://rextester.com/JEXJ27486

更新: 看来我找到了解决方案。比 static_assert 更灵活。 如果无法在部分特化中使用默认模板参数 - 添加额外的 class 全模板特化。

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

示例:http://rextester.com/KTDXNJ90374

一个好的 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!
 }

如果您想要针对不同的派生组类型使用不同的 tuple_size 类,我的先例解决方案(基于 static_assert())将不起作用。

我根据std::conditionalstd::is_base_of

提出以下解决方案

棘手的(我想也是丑陋的)部分是 struct baseType,给定类型,检测碱基(在 FooBar 之间) , 如果可能的话。

#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
 }

如果您可以使用建议但尚未标准化的语言功能,这似乎可以在带有 -fconcepts 标志的 gcc 6.1 下工作:

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