编译时对嵌套模板参数求和

Sum Nested Template Parameters at Compile Time

本文关键字:参数 求和 嵌套 编译      更新时间:2023-10-16

我正在寻找一种更好的方法来计算与嵌套模板类关联的数字模板参数的总和。我这里有一个有效的解决方案,但我想这样做而不必创建这个额外的帮助程序模板类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模板和专业化似乎是一个混乱的黑客。

我知道我也可以创建具有不同定义的 A 本身的专用化Depth(),但由于 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的答案,因为它最直接地提供了我最初要求的东西。

你说:

我想这样做而不必创建这个额外的帮助程序模板类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>

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

选项 #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;
};

演示

选项 #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;
};

演示 2

您可以完全非侵入性地执行此操作,我认为这是有利的:

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 的解决方案那样没有任何模板模板参数,很容易与其他类等一起扩展。