我在使用 Visual C++ 时也遇到过这种情况,我认为在这方面Visual C++ 编译器不符合 C++17 标准并且您的代码是正确的(但您的代码无法与std::vector
使用自定义分配器!)。标准容器实际上有两个模板参数:值类型和分配器(默认为std::allocator<T>
)。 C++17 之前模板模板匹配要求模板参数完全匹配C++17这很宽松,包括默认参数以及。然而出于某种原因,Visual C++ 似乎仍然期待第二个模板参数std::allocator<T>
并且不假设给定的默认参数。
以下部分将更详细地讨论不同标准的模板匹配。在文章的结尾,我将建议替代方案,使您的代码在所有上述编译器上进行编译,其形式为SFINAE对于 C++17 有两个两个模板参数(这样它也可以与自定义分配器一起使用)std::span
适用于 C++20 及以上版本。std::span
实际上根本不需要任何模板。
模板参数std::
集装箱
正如文章中指出的,您链接了已经标准库的容器,例如std::vector https://en.cppreference.com/w/cpp/container/vector, std::deque https://en.cppreference.com/w/cpp/container/deque and std::list https://en.cppreference.com/w/cpp/container/list实际上有多个模板参数。第二个参数Alloc
是一个策略特征,描述内存分配并具有默认值std::allocator<T>
.
template<typename T, typename Alloc = std::allocator<T>>
相反std::array https://en.cppreference.com/w/cpp/container/array实际上使用了两个模板参数T
对于数据类型和std::size_t N
对于容器尺寸。这意味着如果有人想编写一个涵盖所有所述容器的函数,则必须转向迭代器 https://stackoverflow.com/questions/53252321/how-to-write-a-function-that-can-take-in-an-array-or-a-vector。仅在 C++20 中,有用于连续对象序列的类模板std::span https://en.cppreference.com/w/cpp/container/span(这是一种封装了上述所有内容的超级概念),可以放松这一点。
模板模板匹配和C++标准
当编写一个模板参数本身依赖于模板参数的函数模板时,您必须编写一个所谓的模板模板函数,即以下形式的函数:
template<template<typename> class T>
请注意,严格按照标准模板,模板参数必须使用class
不与typename
C++17 之前。您当然可以通过一个非常小的解决方案(例如(Godbolt https://godbolt.org/z/dTMebMnd9)
template<typename Cont>
void f (Cont const& cont) {
using T = Cont::value_type;
return;
}
假设容器包含静态成员变量value_type
然后用于定义元素的基础数据类型。这对所有人都有效std::
容器(包括std::array
!)但不是很干净。
对于模板模板函数,存在特定的规则,这些规则实际上从 C++14 更改为 C++17:C++17 之前模板模板参数必须是一个模板,其参数与其替换的模板模板参数的参数完全匹配。默认参数例如第二个模板参数std::
容器,上述std::allocator<T>
, were 不考虑(参见“模板模板参数”部分here https://en.cppreference.com/w/cpp/language/template_parameters以及“模板模板参数”部分ISO 规范工作草案第 317 页 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf#page=325 or 最终的 C++17 ISO 规范 https://www.iso.org/standard/68564.html):
将模板模板参数 A 与模板模板相匹配
参数P,A的每个模板参数必须匹配
精确对应P的模板参数(直到 C++17)磷
必须至少与 A 一样专业(自 C++17 起).
形式上,模板模板参数 P 至少是专门化的
作为模板模板参数 A if,将以下重写为
两个函数模板,P对应的函数模板在
至少与 A 对应的函数模板一样专业
根据函数模板的部分排序规则。给定
一个发明的类模板 X,其模板参数列表为 A
(包括默认参数):
- 两个函数模板均具有相同的模板参数,分别为 P 或 A。
- 每个函数模板都有一个函数参数,其类型是 X 的特化,模板参数对应于
来自相应函数模板的模板参数,其中,对于
模板参数列表中的每个模板参数PP
函数模板,形成对应的模板实参AA。如果
PP声明了一个参数pack,那么AA就是pack扩展PP...;
否则,AA 是 id-表达式 PP。
如果重写产生无效类型,则 P 至少不等于
专门为 A.
因此,在 C++17 之前,人们必须编写一个模板来提及手动分配器作为默认值如下。这也适用于 Visual C++,但以下所有解决方案都将排除std::array
(上帝螺栓MSVC https://godbolt.org/z/hef3Yv5K3):
template<typename T,
template <typename Elem,typename Alloc = std::allocator<Elem>> class Cont>
void f(Cont<T> const& cont) {
return;
}
你也可以在 C++11 中实现同样的事情可变参数模板(这样数据类型是模板参数包的第一个模板参数,分配器是第二个模板参数T
) 如下 (上帝螺栓MSVC https://godbolt.org/z/eG45x8ns4):
template<template <typename... Elem> class Cont, typename... T>
void f (Cont<T...> const& cont) {
return;
}
现在,在 C++17 中,实际上以下几行应该编译并适用于所有std::
容器与std::allocator<T>
(参见第 83-88 页第 5.7 节,特别是第 85 页上的“模板模板匹配”《C++ 模板:完整指南(第二版)》范德沃德等人。 https://rads.stackoverflow.com/amzn/click/com/0321714121, 戈德螺栓海湾合作委员会 https://godbolt.org/z/jK418eers).
template<typename T, template <typename Elem> typename Cont>
void f (Cont<T> const& cont) {
return;
}
寻求通用的std::
容器模板
现在,如果您的目标是使用仅保存整数作为模板参数的通用容器,并且您必须保证它也可以在 Visual C++ 上编译,那么您有以下选项:
-
您可以使用以下方法扩展简约的不干净版本static_assert
确保您使用正确的值类型(Godbolt https://godbolt.org/z/3Ejo5zdax)。这应该适用于所有类型的分配器以及std::array
但不是很干净。
template<typename Cont>
void f (Cont const& cont) {
using T = Cont::value_type;
static_assert(std::is_same<T,int>::value, "Container value type must be of type 'int'");
return;
}
-
您可以添加std::allocator<T>
作为默认模板参数,其缺点是,如果有人使用带有自定义分配器的容器,并且两者都无法使用,则您的模板将无法工作std::array
(Godbolt https://godbolt.org/z/Tf4szfrMn):
template<template <typename Elem,typename Alloc = std::allocator<Elem>> class Cont>
void f(Cont<int> const& cont) {
return;
}
-
与您的代码类似,您可以自己指定分配器作为第二个模板参数。同样,这不适用于其他类型的分配器(Godbolt https://godbolt.org/z/vhj57MM9E):
template<template <typename... Elem> class Cont>
void f(Cont<int, std::allocator<int>> const& cont) {
return;
}
-
因此,C++20 之前最简洁的方法可能是使用SFINAE https://eli.thegreenplace.net/2014/sfinae-and-enable_if/到 SFINAE out(意味着您在模板内添加某种结构,如果不满足您的要求,该结构将生成编译文件)所有其他不使用该数据类型的实现int
with type_traits
(std::is_same
from #include <type_traits>
, Godbolt https://godbolt.org/z/fnxKqxeof)
template<typename T, typename Alloc,
template <typename T,typename Alloc> class Cont,
typename std::enable_if<std::is_same<T,int>::value>::type* = nullptr>
void f(Cont<T,Alloc> const& cont) {
return;
}
或者哪些不是整数类型 (std::is_integral
, Godbolt https://godbolt.org/z/1bMEfoveT)因为这对于模板参数来说更加灵活Alloc
:
template<typename T, typename Alloc,
template <typename T,typename Alloc> class Cont,
typename std::enable_if<std::is_integral<int>::value>::type* = nullptr>
void f(Cont<T,Alloc> const& cont) {
return;
}
此外,这可以是。从 C++14 开始,我们还可以使用相应的别名并编写std::enable_if_t<std::is_same_v<T,int>>
代替std::enable_if<std::is_same<T,int>::value>::type
这使得阅读起来不那么尴尬。
-
终于符合最新标准了C++20您甚至应该能够使用期待已久的概念(#include <concepts>) https://en.cppreference.com/w/cpp/language/constraints使用集装箱概念 https://en.cppreference.com/w/cpp/named_req/Container(另请参阅此Stackoverflow 帖子 https://stackoverflow.com/questions/60449592/how-do-you-define-a-c-concept-for-the-standard-library-containers) 例如如下 (Wandbox https://wandbox.org/permlink/vKNrcdpcxzRTT33S)
template<template <typename> typename Cont>
requires Container<Cont<int>>
void f(Cont<int> const& cont) {
return;
}
-
C++20 中也存在类似的情况std::span<T>
与上述所有解决方案不同的是std::array
还有(Wandbox https://wandbox.org/permlink/sdNiEgppOJHZ2Ex0)
void f(std::span<int> const& cont) {
return;
}