在编译时汇总嵌套模板参数

时间:2016-04-21 18:02:47

标签: c++ templates c++11

我正在寻找一种更好的方法来计算与嵌套模板类相关的数值模板参数的总和。我在这里有一个可行的解决方案,但是我想这样做不用必须创建这个额外的帮助模板类DepthCalculator和部分特化DepthCalculator<double,N>

#include <array>
#include <iostream>

template<typename T,size_t N>
struct DepthCalculator
{
  static constexpr size_t Calculate()
  {
    return N + T::Depth();
  }
};

template<size_t N>
struct DepthCalculator<double,N>
{
  static constexpr size_t Calculate()
  {
    return N;
  }
};

template<typename T,size_t N>
class A
{
  std::array<T,N> arr;
public:
  static constexpr size_t Depth()
  {
    return DepthCalculator<T,N>::Calculate();
  }
  // ...
  // Too many methods in A to write a separate specialization for.
};

int main()
{
  using U = A<A<A<double,3>,4>,5>;
  U x;
  constexpr size_t Depth = U::Depth(); // 3 + 4 + 5 = 12
  std::cout << "Depth is " << Depth << std::endl;
  A<double,Depth> y;
  // Do stuff with x and y
  return 0;
}

静态函数A::Depth()在编译时返回适当的深度,然后可以将其用作参数来创建A的其他实例。只是为了这个目的,必须创建DepthCalculator模板和专门化,这似乎是一个混乱的黑客。

我知道我也可以使用Depth()的不同定义来创建A本身的特化,但由于A中的方法数量很多,这更加混乱,其中大多数依赖于模板参数。另一个选择是从A继承然后专门化子类,但对于看起来应该更简单的东西来说,这似乎也过于复杂。

使用C ++ 11是否有更清洁的解决方案?

摘要编辑

最后,这是我在工作项目中使用的解决方案:

#include <array>
#include <iostream>

template<typename T,size_t N>
class A
{
  std::array<T,N> arr;

  template<typename U>
  struct Get { };
  template<size_t M>
  struct Get<A<double,M>> { static constexpr size_t Depth() { return M; } };
  template<typename U,size_t M>
  struct Get<A<U,M>>
    { static constexpr size_t Depth() { return M + Get<U>::Depth(); } };

public:
  static constexpr size_t GetDepth()
  {
    return Get<A<T,N>>::Depth();
  }
  // ...
  // Too many methods in A to write a separate specialization for.
};

int main()
{
  using U = A<A<A<double,3>,4>,5>;
  U x;
  constexpr size_t Depth = U::GetDepth(); // 3 + 4 + 5 = 12
  std::cout << "Depth is " << Depth << std::endl;
  A<double,Depth> y;
  // Do stuff with x and y
  return 0;
}

Nir ​​Friedman提出了一些关于为什么GetDepth()应该是外部函数的好点,但是在这种情况下还有其他Get函数(未显示),这些函数是适当的成员函数,因此它会使最有意义的是GetDepth()成员函数。我还借用了Nir的想法,让Depth()函数只调用自己,而不是GetDepth(),它创建了一些不那么循环的依赖。

我选择了skypjack的答案,因为它最直接地提供了我最初要求的内容。

3 个答案:

答案 0 :(得分:2)

选项#1

重新定义您的特征如下:

#include <array>
#include <cstddef>

template <typename T>
struct DepthCalculator
{
    static constexpr std::size_t Calculate()
    {
        return 0;
    }
};

template <template <typename, std::size_t> class C, typename T, std::size_t N>
struct DepthCalculator<C<T,N>>
{
    static constexpr size_t Calculate()
    {
        return N + DepthCalculator<T>::Calculate();
    }
};

template <typename T, std::size_t N>
class A
{
public:
    static constexpr size_t Depth()
    {
        return DepthCalculator<A>::Calculate();
    }

private:
    std::array<T,N> arr;
};

DEMO

选项#2

将特征更改为函数重载:

#include <array>
#include <cstddef>

namespace DepthCalculator
{
    template <typename T> struct tag {};

    template <template <typename, std::size_t> class C, typename T, std::size_t N>
    static constexpr size_t Compute(tag<C<T,N>>)
    {
        return N + Compute(tag<T>{});
    }

    template <typename T>
    static constexpr size_t Compute(tag<T>)
    {
        return 0;
    }
}

template <typename T, std::size_t N>
class A
{
public:
    static constexpr std::size_t Depth()
    {
        return Compute(DepthCalculator::tag<A>{});
    }

private:    

    std::array<T,N> arr;
};

DEMO 2

答案 1 :(得分:2)

你说:

  

我想这样做,而不必创建额外的帮助模板类DepthCalculator

所以,也许这个(最小的,有效的例子)对你来说很好:

#include<type_traits>
#include<cassert>

template<class T, std::size_t N>
struct S {
    template<class U, std::size_t M>
    static constexpr
    typename std::enable_if<not std::is_arithmetic<U>::value, std::size_t>::type
    calc() {
        return M+U::calc();
    }

    template<typename U, std::size_t M>
    static constexpr
    typename std::enable_if<std::is_arithmetic<U>::value, std::size_t>::type
    calc() {
        return M;
    }

    static constexpr std::size_t calc() {
        return calc<T, N>();
    }
};

int main() {
    using U = S<S<S<double,3>,4>,5>;
    static_assert(U::calc() == 12, "oops");
    constexpr std::size_t d = U::calc();
    assert(d == 12);
}

我不确定我到底是不是你的问题 希望这可以提供帮助。

如果您使用的是C ++ 14,还可以使用:

template<class U, std::size_t M>
static constexpr
std::enable_if_t<not std::is_arithmetic<U>::value, std::size_t>

如果您使用的是C ++ 17,则会变为:

template<class U, std::size_t M>
static constexpr
std::enable_if_t<not std::is_arithmetic_v<U>, std::size_t>

这同样适用于其他 sfinae d返回类型。

答案 2 :(得分:1)

你可以完全非侵入性地做到这一点,我认为这是有利的:

template <class T>
struct Depth
{
    constexpr static std::size_t Calculate()
    {
        return 0;
    }
};

template <class T, std::size_t N>
struct Depth<A<T, N>>
{
    constexpr static std::size_t Calculate()
    {
        return N + Depth<T>::Calculate();
    }
};

用法:

using U = A<A<A<double,3>,4>,5>;
constexpr size_t depth = Depth<U>::Calculate(); // 3 + 4 + 5 = 12

我意识到你原来的问题是如何在没有额外的“帮助模板”的情况下做到这一点,我的解决方案仍然存在。但另一方面,它将功能完全移出A本身,因此它不再是一个辅助模板,它只是一个模板。这很短,没有任何模板模板参数,不像Piotr的解决方案,很容易与其他类扩展等。