繁体   English   中英

C++14 变量模板:它们的目的是什么? 任何使用示例?

[英]C++14 Variable Templates: what is their purpose? Any usage example?

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

除此之外,我们可以通过将变量包装在模板结构或类中来拥有此功能,这如何与类型转换混合? 我看到有些重叠。

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

除了 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 ,则它是只读的。 如果它是一个constexpr ,就像所有的constexpr声明一样,它在constexpr (resions) 之外没有太多用处。

除此之外,我们可以通过将变量包装在模板结构或类中来拥有此功能,这如何与类型转换混合?

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

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

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

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

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

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

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

...

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

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

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

...

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

我们只需将变量包装在模板结构或类中即可拥有此功能

是的,但这将是无偿的语法盐。 对血压不健康。

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

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代替,但重点是有一整类函数共享此行为。

我想知道是否有可能发生这些事情:(假设模板 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(double)的类型radians ,您可以编写pi<radians>

好吧,您可以使用它来编写这样的编译时代码:

#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。

我这里有一个用例。

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>);
    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM