【问题标题】:Does template (meta)programming has always only one way of implementation?模板(元)编程是否总是只有一种实现方式?
【发布时间】:2011-06-19 22:37:17
【问题描述】:

研究了少数template 程序,尤其是将编译时结果推导出为常数的元程序,我了解到通常只有一种方法可以实现某物。例如:像factorial 示例一样简单或像is_base_of 一样复杂

我永远无法考虑完全不同逻辑的代码的完美替代实现。这是一个真实的假设吗?

如果这个假设是正确的,这意味着,无论何时我们使用模板技巧来实现某些东西,我们总是可以确信,这是最好的代码,我们不必再担心优化编译时间了。

[注意:我没有提到我们对class 和函数所做的一般template 用法。但是用于推导编译时间常数的用法。]

【问题讨论】:

  • 你的意思是只有一种方式来实现模板,还是只有一种方式来实现代码使用模板?
  • @Ben Hocking,第二个。只有一种使用template 进行编码的方法。您可以以我上面链接的is_base_of 为例。它只有使用模板来实现的方法。事实上,我试图实现自己stackoverflow.com/questions/5770467/…。具有完全不同的逻辑,但仍然不是完美的选择。
  • 您是在问标准中是否定义了模板实例化递归顺序?
  • 您真的在问是否存在可以以两种不同方式实现的功能?这听起来像是一个奇怪的问题。
  • 使用模板元编程实现功能所需的技能与纯函数式编程所需的技能相同。因此,这个问题归结为:在纯函数式编程中是否总是只有一种实现(某事)的方式?

标签: c++ templates optimization metaprogramming


【解决方案1】:

您倾向于只看到一种做事方式的原因是,人们实际使用模板元编程的事情通常在算法上是微不足道的——它们只是看起来很复杂,因为它们与大量的黑客行为和C++ 模板语法的怪异之处。

但有时(正如史蒂夫·杰索普的回答所示)确实有多种算法可以计算某些东西,您可以使用模板来实现其中的任何一种。

作为另一个例子,这里有两种方法来评估pow(a,b)(对于小整数参数):

很明显:

// ----- Simple Way -----

template <int A, int B>
struct PowA {
    typedef PowA<A,B-1> next;
    enum {
       value = A * next::value,
       recursion_count = 1 + next::recursion_count
    };
};
template <int A> struct PowA<A, 1> { enum { value = A, recursion_count = 0 }; };
template <int A> struct PowA<A, 0> { enum { value = 1, recursion_count = 0 }; };

Slightly less obvious:

// ----- Less Simple Way -----

template <int A, int B, int IsOdd> struct PowHelper;

template <int A> struct PowHelper<A, 0, 0> { enum { value = 1, recursion_count = 0 }; };
template <int A> struct PowHelper<A, 1, 1> { enum { value = A, recursion_count = 0 }; };

template <int A, int B>
struct PowHelper<A, B, 1> {
    typedef PowHelper<A, B-1, 1> next;
    enum {
        value = A * next::value,
        recursion_count = 1 + next::recursion_count
    };
};
template <int A, int B>
struct PowHelper<A, B, 0> {
    typedef PowHelper<A, B/2, ((B/2)&1)> next;
    enum {
        x = next::value,
        value = x*x,
        recursion_count = 1 + next::recursion_count
    };
};

template <int A, int B>
struct PowB {
    typedef PowHelper<A,B,(B & 1)> helper;
    enum {
        value = helper::value,
        recursion_count = helper::recursion_count
    };
};

还有一些代码让你检查一下:

// ----- Test -----

#include <iostream>

int main(int argc, char* argv[]) {
#define CHECK(X,Y) \
    std::cout << ("PowA: " #X "**" #Y " = ") << \
        PowA<(X),(Y)>::value << " (recurses " << \
        PowA<(X),(Y)>::recursion_count << " times)" << std::endl; \
    std::cout << ("PowB: " #X "**" #Y " = ") << \
        PowB<(X),(Y)>::value << " (recurses " << \
        PowB<(X),(Y)>::recursion_count << " times)" << std::endl;

    CHECK(3,3)
    CHECK(2,8)
    CHECK(7,3)
    CHECK(3,18)

#undef CHECK

   return 0;
}

【讨论】:

  • 我在第二次混乱中看到x*x,所以我将假设不检查它是否是通过平方和撤退到安全位置的幂运算。 +1,因为在实现的简单性和预期的性能(在编译时间和递归深度方面,这在 TMP 中通常相当有限)之间存在明显的权衡。
【解决方案2】:

有点取决于您所说的“以不同的方式做事”是什么意思。以下是计算三角形数的两种 TMP 实现:

template<int N>
struct RecursiveTriangle {
    static const int value = RecursiveTriangle<N-1>::value + N;
};

template<>
struct RecursiveTriangle<0> {
    static const int value = 0;
};

template<int N>
struct Triangle {
    static const int value = (N*(N+1))/2;
};

这些恰好类似于两种“不同”的命令式计算三角形数的方法——使用循环或使用与Triangle 相同的公式。不过,它们的定义域不同 - Triangle 处理负数,而 RecursiveTriangle 不处理。并不是说Triangle 的负数结果很有意义。

那么,“不同的方法”是什么意思?

【讨论】:

  • +1 这实际上是一个很好的例子。然而,大多数人会立即选择第二种解决方案。
  • @iammilind:是的,在这种情况下,一种算法显然优于另一种算法。但它肯定表明,在一般的函数式编程中,特别是在 TMP 中,实现不同的算法以产生相同的结果是可能的。在实践中,如果在 TMP 情况下没有选择有用的算法,那可能是因为在实践中 TMP 通常不用于做任何特别复杂的事情。如果您正在实施 Boost.Spirit,那么您可能对是否有一个明显的解决方案有不同的想法。
【解决方案3】:

一般而言,TMP 中缺乏可用的功能以及功能的相对简单性(如果不是必须表达的方式)意味着很少有多个实现存在显着差异 .

【讨论】:

  • 如果您要断言实现的唯一性,无论这意味着什么,您都需要一个证明。另一方面,否认所有个人需求的独特性是一个反例。
  • 这是公认的答案?!我想这是一个奇怪问题的合乎逻辑的结论。
  • @David Hefferman:我从来没有说过 all TMP 结构只有一种方式,我明确表示“显着”。我说差异显着是非常罕见的。
  • 我认为为任何 TMP 构造构建显着不同的实现会很容易。的确,这样的结构总是荒谬可笑的人为和奇异的,但那又怎样?
【解决方案4】:

让我们从Wikipedia获取factorial的代码:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

你也可以这样实现:

template <int N>
struct Factorial 
{
    enum { value = Factorial<N - 1>::value * N };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

或者作为:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<1> 
{
    enum { value = 1 };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

【讨论】:

  • 看起来我们有相似的想法!
  • @Ben 是的,但你比我早一分钟搞定了!
  • 和@Ben 一样:这两个在功能上相同
  • @DeadMG:当然它们在功能上是相同的。他们计算相同的功能,这就是功能相同的意思。根据这个定义,在命令式编程中也只有一种实现阶乘的方法。我认为提问者很困惑,认为“不同的方式来做”是一个定义明确的概念。
  • @Steve,我的问题可能不清楚,但我的意思是,我们不能在模板编程中有多种方法来计算一个常数值。方式应该完全不同。
【解决方案5】:

不,不只有一种实现。以下是两种执行阶乘的示例:

#include <iostream>
using namespace std;

template <int N>
struct Factorial
{
  enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0>
{
  enum { value = 1 };
};

template <int N>
class FactorialC {
public:
  static const long value = N * Factorial<N - 1>::value;
};

template <>
class FactorialC<10> {
public:
  static const long value = 3628800;
};

template <>
class FactorialC<0> {
public:
  static const long value = 0;
};

int main() {
  cout << Factorial<4>::value << endl;
  cout << Factorial<12>::value << endl;
  cout << FactorialC<4>::value << endl;
  cout << FactorialC<12>::value << endl;
}

输出:

24
479001600
24
479001600

【讨论】:

  • @David Heffernan:很好(我已经编辑过了)。我最初是按相反的顺序写的。
  • 实际上并没有什么不同。它们实际上是相同的。
  • 你们中的任何一个能否描述“实际上相同”的含义,以及“不同”的样子,还是“我看到它就知道”的问题?采用众所周知的“相同”定义,一个定义规则说它们不一样(如果 FactorialC 被重命名为 Factorial 并且两者出现在同一程序的不同 TU 中)。将“我看到它就知道”与“当我看到它就知道的东西,不存在”这样的明确陈述相结合的明显问题是,它强烈诱使你成为“不是真正的苏格兰人”谬误。
猜你喜欢
  • 2012-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-08
相关资源
最近更新 更多