C++ 限制模板模板中的内部类型
C++ Restricting the inner type in a template template
有没有办法在C++中指定嵌套模板中内部模板的类型?我想做的是
template<template <std::string> class StringContainer>
void function(const StringContainer& cont);
换句话说,我想要一个接受所有类型的字符串容器的函数——vector
s、deque
s、list
s等。
基本上,我有四个问题:
- 可以这样做吗?
- 如果是这样,定义应该是什么样子的(上面没有编译,但我没想到会)?
- 即使可能,有没有办法让它与标准容器一起使用,这些容器具有多个模板参数,甚至可能是特定于实现的?
- 如果不是容器,我至少可以期望迭代器只有一个模板参数吗?
谢谢!
#include <deque>
#include <list>
#include <iostream>
#include <vector>
// Removed:
// template < template<typename, typename ...> class Container,
// typename T, typename ...Types>
// void function(const Container<T, Types...>&);
template < template<typename ...> class Container, typename ...Types>
void function(const Container<std::string, Types...>& container) {
for(const std::string& s: container) {
std::cout << s << " ";
}
std::cout << std::endl;
}
int main () {
std::deque<std::string> d{ "Hello", "Deque" };
function(d);
std::list<std::string> l{ "Hello", "List" };
function(l);
std::vector<std::string> v{ "Hello", "Vector" };
function(v);
return 0;
}
这是可能的,但你要走的方向不是一个明智的方向。
相反,我建议通过鸭子打字来思考事情。 如果你的函数需要迭代传入的东西的内容,并期望内容是字符串,你正在寻找的是"这个参数匹配std::string
序列的概念"。
最好在 C++14 中通过 require 语法完成此操作,但您可能还没有兼容 C++14 的编译器,除非您通过时间机器发布。
我们可以在 C++11 中通过特征类、SFINAE 和 enable_if
来做到这一点。 或者,我们可以在 C++11 中通过特征类和标签调度来做到这一点。 标签调度没有 SFINAE 那么晦涩难懂,所以我将在这里演示它。
我们从一个泛型函数开始:
template<typename C>
void function( C const& c )
接受任何东西。 接下来,我们编写一些特征类:
template<typename C,typename=void>
struct is_string_sequence : std::false_type {};
template<typename C>
struct is_string_sequence<C, typename std::enable_if<TODO STUFF HERE>::type> : std::true_type {};
这告诉我们传入的参数是否是字符串上的序列。
接下来,我们编写一个辅助函数:
template<typename C>
void function_helper( C const& c, std::true_type string_sequence_test ) {
// your code goes here, and can assume c is a string sequence
}
并将第一个转发给它:
template<typename C>
void function( C const& c ) {
function_helper( c, is_string_sequence<C>() );
}
但我们也创建一个标签以传递给帮助程序。 如果我们的测试通过,则此标记为真,如果未通过,则为假。 由于我们只有 true 的覆盖,因此 false 大小写会产生错误,并且该错误往往是合理的可读性的。
唯一剩下的就是写TODO STUFF HERE
.
判断某物是否是 C++11 中的字符串序列的鸭子类型方法是查看是否:
for( std::string s : c ) {
}
编译。 执行此操作的一种方法是查看在 std::begin
和 std::end
可见的上下文中应用于c
的 begin
和end
自由函数是否返回迭代器,并且这些迭代器在取消引用时生成可转换为 std::string
的类型。
这需要一些体操。 首先,查看在该特定上下文中调用begin
和end
的结果:
namespace adl_helper {
using std::begin; using std::end;
template<typename C>
auto adl_begin( C&& c )->decltype( begin(std::forward<C>(c)) );
template<typename C>
auto adl_end( C&& c )->decltype( end(std::forward<C>(c)) );
}
using adl_helper::adl_begin; using adl_helper::adl_end;
请注意,这些没有主体:出于我们的目的,我们不需要主体,我们只需要返回类型。
现在我们可以问"东西是容器吗?
template<typename C, typename=void>
struct is_container : std::false_type {};
template<typename C>
struct is_container<C, typename=typename std::enable_if<
std::is_convertible<
decltype( adl_begin( std::declval<C&>() ) == adl_end( std::declval<C&>() ) ),
bool
>::value
>::type> : std::true_type
{
typedef decltype( adl_begin( std::declval<C&>() ) ) iterator;
};
一个更高级的人可能会iterator_traits
来探索(上面,我只是检查begin
和end
是否有效,并且结果在它们之间有一个覆盖的==
,返回可以转换为bool
的东西)。
然后,我们可以询问生成的迭代器是否具有可以在另一个特征类中转换为std::string
的值类型。
或者我们可以做一个decltype( *adl_begin( std::declval<C&>() ) )
,看看它是否可以直接转换为std::string
,并假设begin
是否有效,end
。
SFINAE 方法与此类似,不同之处在于我们没有将标记分派给帮助程序,而是将测试直接放入function
签名中,包装在激活或停用function
以解决过载问题的enable_if
中。 这会产生稍微难以阅读的错误,但根据我的经验,在调用堆栈中的较高点。
不会按照你的目标方法去做,因为容器是如何从其他类型的类型构建的细节并不重要:重要的是你如何使用它。
如果你需要除迭代之外的其他功能,你可以编写其他鸭子类型测试来确定你是否可以擦除、插入等。 但是,请注意,关联容器和顺序容器是非常不同的东西,统一处理它们很少是一个好主意,除了两者都是序列。
- 嵌套模板函数采用 2 个具有相同内部类型的容器
- 在变量名后声明带有 () 的非内部类型与不使用变量名的行为不同。即 std::map<int,char>x(); - 这是怎么回事?
- 如何为模板的内部类型专用化类
- 从内部类型参数推断外部类型
- 在 CUDA 中使用内部类型转换的奇怪错误
- std::enable_if使用它的内部类型,而不使用它
- 编译器找不到内部类型
- 内部类型定义和循环依赖项
- C++ 限制模板模板中的内部类型
- 部分默认使用内部类型初始化模板模板参数
- 使用专用类中的类模板的内部类型
- 使用内部类型声明
- 从匿名指针类型中检索内部类型
- 内部类型作为模板参数
- 编写泛型包装器:有条件地将模板参数中的不同类型映射到单个类内部类型
- 如何在C++中推导内部类型的外部类型
- c++ std::map内部类型初始化使用[]操作符
- 容器使用的内部类型的内存分配
- 使用内部类型作为模板参数继承模板类
- 如何获取n嵌套向量的最内部类型