69

C++14 将允许创建模板化的变量。通常的例子是一个变量“pi”,可以读取它以获得各种类型的数学常数 π 的值(3 表示int;最接近的值可能与float等)

除了我们可以通过在模板化结构或类中包装变量来获得此功能,这如何与类型转换混合使用?我看到一些重叠。

除了 pi 示例之外,它如何与非常量变量一起使用?任何使用示例来了解如何充分利用此类功能及其目的是什么?

4

6 回答 6

29

除了 pi 示例之外,它如何与非常量变量一起使用?

目前,它似乎为类型单独实例化变量。即,您可以将 10 分配给n<int>它,它与模板定义不同。

template<typename T>
T n = T(5);

int main()
{
    n<int> = 10;
    std::cout << n<int> << " ";    // 10
    std::cout << n<double> << " "; // 5
}

如果声明是const,它是只读的。如果它是 a constexpr,就像所有声明一样,它在外部(regressions)constexpr没有多大用处。constexpr

除了我们可以通过在模板化结构或类中包装变量来获得此功能,这如何与类型转换混合使用?

这是一个简单的提议。我无法看到它如何显着影响类型转换。正如我已经说过的,变量的类型是您实例化模板的类型。即,decltype(n<int>)是int。decltype((double)n<int>)是双等。

任何使用示例来了解如何充分利用此类功能及其目的是什么?

N3651提供了一个简洁的理由。

唉,现有的 C++ 规则不允许模板声明来声明变量。这个问题有众所周知的解决方法:

• 使用类模板的 constexpr 静态数据成员

• 使用返回所需值的 constexpr 函数模板

这些变通方法已为人所知数十年,并且有据可查。std::numeric_limits 等标准类是典型示例。尽管这些变通方法并不完美,但它们的缺点在某种程度上是可以容忍的,因为在 C++03 时代,只有简单的内置类型常量才能享受不受限制的直接和高效的编译时支持。随着 C++11 中 constexpr 变量的采用,所有这些都发生了变化,这将直接和有效的支持扩展到了用户定义类型的常量。现在,程序员正在使(类类型的)常量在程序中越来越明显。因此,增加与变通办法相关的困惑和挫折感。

...

“静态数据成员”的主要问题是:

• 它们需要“重复”声明:一次在类模板内,一次在类模板外,以提供“真正的”定义,以防使用 odr 常量。

• 程序员对提供两次相同声明的必要性感到既恼火又困惑。相比之下,“普通”常量声明不需要重复声明。

...

此类别中众所周知的示例可能是 numeric_limits 的静态成员函数,或者诸如 boost::constants::pi<T>()等。 Consexpr 函数模板不会遇到静态数据成员所具有的“重复声明”问题;此外,它们提供了功能抽象。但是,它们迫使程序员在定义站点预先选择常量的传递方式:通过 const 引用,或者通过普通的非引用类型。如果通过 const 引用传递,则必须在静态存储中系统地分配常量;如果是非引用类型,那么常量需要复制。复制对于内置类型来说不是问题,但对于具有值语义的用户定义类型来说,它是一个很好的选择,而这些类型不仅仅是微小的内置类型(例如矩阵、整数或 bigfloat 等)的包装器。相比之下,“普通的” const(expr) 变量不会遇到这个问题。

于 2014-01-16T13:34:11.550 回答
28

我们可以通过在模板结构或类中包装一个变量来拥有这个特性

是的,但那将是无端的句法盐。对血压不健康。

pi<double>比 更能传达意图pi<double>::value。精炼到位。这足以在我的书中允许和鼓励这种语法。

于 2014-01-16T13:20:04.573 回答
11

C++14 的变量模板的另一个实际示例是当您需要一个函数来将某些内容传递给std::accumulate

template<typename T>
T const & (*maxer) (T const &, T const &) = std::max<T>;

std::accumulate(some.begin(), some.end(), initial, maxer<float>);

请注意,使用std::max<T>是不够的,因为它无法推断出确切的签名。在这个特定的示例中,您可以max_element改用,但关键是有一整类函数共享此行为。

于 2015-08-05T20:32:35.127 回答
7

我想知道这些方面的事情是否可能:(假设模板 lambdas 的可用性)

void some_func() {
    template<typename T>
    std::map<int, T> storage;

    auto store = []<typename T>(int key, const T& value) { storage<T>[key] = value; };

    store(0, 2);
    store(1, "Hello"s);
    store(2, 0.7);

    // All three values are stored in a different map, according to their type. 
}

现在,这有用吗?

作为更简单的用法,请注意pi<T>使用显式转换(一元构造函数的显式调用)的初始化,而不是统一初始化。这意味着,给定radians具有构造函数的类型radians(double),您可以编写pi<radians>.

于 2014-01-16T13:06:49.403 回答
4

好吧,您可以使用它来编写如下编译时代码:

#include <iostream>

template <int N> const int ctSquare = N*N;

int main() {
    std::cout << ctSquare<7> << std::endl;
}

这是对等价物的显着改进

#include <iostream>

template <int N> struct ctSquare {
    static const int value = N*N;
};

int main() {
    std::cout << ctSquare<7>::value << std::endl;
}

在引入变量模板之前,人们用来编写模板元编程。对于非类型值,我们从 C++11 开始就可以做到这一点constexpr,因此模板变量仅具有允许基于类型对变量模板进行计算的优势。

TL;DR:它们不允许我们做以前不能做的任何事情,但它们使模板元编程不再是 PITA。

于 2017-09-19T14:42:49.360 回答
0

我这里有一个用例。

template<typename CT> constexpr CT MARK = '%';
template<> constexpr wchar_t MARK<wchar_t> = L'%';

在字符串处理模板中使用。`

template <typename CT> 
void ProcessString(const std::basic_string<CT>& str)
{
    auto&& markpos = str.find(MARK<CT>);
    ...
}
于 2019-10-19T17:51:32.377 回答