23

首选是这样的:

template<typename T>
bool isNotZero(const T &a)
{
    if (std::is_floating_point<T>::value) return abs(a) > std::numeric_limits<T>::epsilon();
    else return a;
}

或这个:?

template<typename T>
std::enable_if<std::is_floating_point<T>::value, bool>::type
isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }

template<typename T>
std::enable_if<std::is_integral<T>::value, bool>::type
isNotZero(const T &a) { return a; }

我通常使用第一种类型来避免许多版本的功能。

我相信它是完全一样的。

第一个版本在操作码阶段优化,第二个版本在模板实例化阶段。

4

2 回答 2

20

我相信它是完全一样的。

我不会说完全一样。

在第一个版本中,您使用的是在运行时评估的条件语句,但决定执行哪个分支的条件可以(并且是)在编译时决定。

因此,无论输入的类型是什么,您的两个分支都必须编译并且将被编译,即使我们在编译时知道其中只有一个会被执行而另一个会死 - 我希望编译器会在这里发出警告。

在第二种情况下,您只编译(当然,执行)适合 input 类型的内容。在我看来,这使得第二种方法更胜一筹。

现在,即使在这种特殊情况下,您选择哪种方法可能没有区别,由编译时条件决定的条件执行应该通过编译时构造来表示 - SFINAE 和模板重载,而if应该用于条件这取决于系统的运行时状态。

例如,如果条件的两个分支包含仅在执行相应分支时才编译的代码,则第一种方法是不可能的。考虑这两种类型:

struct X
{
    X(int) { }
};

struct Y
{
    Y() { }
};

以及以下功能模板:

template<typename T>
T foo(const T &a)
{
    if (std::is_constructible<T, int>::value)
    {
        return T(42);
    }
    else
    {
        return T();
    }
}

现在以下调用都不合法:

foo(X()); // ERROR! X is not default-constructible
foo(Y()); // ERROR! Y is not constructible from an int

仅此一项就表明,一般来说,处理编译时条件执行的适当工具是模板重载 + SFINAE(或可能涉及类模板特化的等效结构)。

当然,有一些退化的案例(比如这个)允许你使用其他工具,但是如果我们正在寻找概念上正确的设计指南,我相信这里有一个明显的赢家。

如果 C++ 中存在类似的东西,事情当然会有所不同static if,但目前情况并非如此——甚至在不久的将来似乎也不会。

于 2013-04-23T20:50:32.877 回答
4

目前,我更喜欢使用 SFINAE。使用 SFINAE 不需要任何优化,因为您明确允许根据情况仅调用其中一个函数。没有优化可以简单地执行,因为将在运行时调用适当的函数而无需做出决定。

使用if条件语句使程序有责任在运行时做出决定。当然,这可以优化掉,但可能不会。无论针对特定模板参数实际执行哪个分支,都可能导致编译两个分支。这意味着对于任何给定的模板参数,每个分支中的代码必须在语法和语义上正确。

也许有一天我们会有一个static if,它可以让你把if语句写成编译时条件,但目前对此有一些强烈的感受

最近为 C++ 提出的static if特性从根本上来说是有缺陷的,它的采用对于该语言来说将是一场灾难。

但是,在不久的将来(目标是围绕 C++14 发布)我们可能会遇到一些限制(又名概念精简版),这将允许您编写如下函数:

template<Floating_point T>
bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }

template<Integral T>
bool isNotZero(const T &a) { return a; }

这相当于写:

template<typename T>
  requires Floating_point<T>()
bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); }

template<typename T>
  requires Integral<T>()
bool isNotZero(const T &a) { return a; }

Floating_pointandIntegral约束只是其模板参数的constexpr谓词,在编译时检查并参与重载决议。这将是编写这样一组函数的首选方式。

于 2013-04-23T20:55:13.927 回答