C ++中是否有一种方法可以在嵌套模板中指定内部模板的类型?我想做的是
template<template <std::string> class StringContainer>
void function(const StringContainer& cont);
换句话说,我想要一个接受所有类型的字符串容器的函数 - vector
s,deque
s,list
s等。
基本上,我有四个问题:
谢谢!
答案 0 :(得分:2)
#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;
}
答案 1 :(得分:1)
这是可能的,但你要走的方向并不明智。
相反,我会建议通过鸭子打字思考问题。如果你的函数需要迭代传入的东西的内容,并期望内容是字符串,你要找的是“这个参数匹配std::string
s序列的概念。”
最好通过require语法在C ++ 14中完成,但是你可能还没有符合C ++ 14的编译器,除非你是通过时间机器发布的。
我们可以通过特征类,SFINAE和enable_if
在C ++ 11中完成此操作。或者,我们可以通过traits类和标签调度在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。因为我们只有true的覆盖,所以false case会产生错误,并且该错误往往具有合理的可读性。
唯一剩下的就是写TODO STUFF HERE
。
在C ++ 11中判断某个字符串序列是否为duck-type的方法是查看:
for( std::string s : c ) {
}
编译。一种方法是查看在begin
和end
可见的上下文中c
和std::begin
自由函数是否应用于std::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
)的内容。
然后我们可以询问生成的迭代器是否具有可以在另一个traits类中转换为std::string
的值类型。
或者,我们可以执行decltype( *adl_begin( std::declval<C&>() ) )
,看看是否可以将其直接转换为std::string
,并假设begin
是否有效,end
。
SFINAE方法类似,除了不将标记调度到帮助程序之外,我们将测试直接放入function
签名,包含在enable_if
中,用于激活或停用{{1}用于重载解析。根据我的经验,这会产生稍微难以读取的错误,但在调用堆栈中处于更高的位置。
你没有采用你所针对的方法,因为从其他类型构建容器的细节并不重要:重要的是你如何使用它。
如果您需要除迭代之外的其他类型的其他功能,您可以编写其他鸭类测试,以确定是否可以擦除,插入等。但是,请注意关联容器和顺序容器是非常不同的东西,并且处理统一它们很少是一个好主意,除了两者都是序列。