如何使函数参数与容器无关

8

我正在编写一个实用函数,它将获取一个元素向量(可以是字符串、整数、双精度浮点数、字符),将它们连接成一个单一的字符串并返回。代码如下:

template<typename T>
std::string convert2Str(std::vector<T> const& vec) 
{
   std::ostringstream sStream; 
   for (size_t k=0; k<vec.size(); ++k) {
      sStream << vec[k] << " "; 
   }
   return sStream.str(); 
}

我希望将此函数更加通用:

  • 首先,使用迭代器而不是使用索引来访问vector<T>。在循环之前,我尝试使用std::vector<T>::const_iterator it = vec.begin(),但编译器报错: : error: expected ; before it 当我将上述定义更改为std::vector<std::string>::const_iterator it = vec.begin()时,错误消失了。因此,看起来我没有遵循正确的语法,请告诉我正确的语法。
  • 其次,通过使第一个参数与容器无关,使函数更加通用。对于任何容器(vectorlistqueuedeque等),我想要执行与上述相同的操作。我尝试在stackoverflow上搜索此内容,但没有找到令人满意的答案。
5个回答

7

第一步,如你所说,使用迭代器:

template<typename T>
std::string convert2Str(std::vector<T> const& vec) 
{
   typedef std::vector<T> container;
   std::ostringstream sStream; 
   for (typename container::const_iterator it = vec.begin(); it != vec.end(); ++it) {
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

第二步,将模板参数设置为容器类型而不是元素类型(可以使用value_type获取元素类型):
template<typename container>
std::string convert2Str(container const& vec)
{
   typedef container::value_type T; // if needed
   std::ostringstream sStream; 
   for (typename container::const_iterator it = vec.begin(); it != vec.end(); ++it) {
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

在C++0x中,这变得更加简单(不需要使用typename):
template<typename container>
std::string convert2Str(container const& vec)
{
   using std::begin;
   using std::end;
   std::ostringstream sStream;
   for (auto it = begin(vec); it != end(vec); ++it) {
      typedef decltype(*it) T; // if needed
      sStream << *it << " "; 
   }
   return sStream.str(); 
}

除其他优点外,std::beginstd::end也适用于原始数组。


他的第一点和你的有什么不同?使用std :: vector <T> :: const_iterator不起作用,但类型定义却可以?我感到困惑。他的解决方案只是在迭代器声明前缺少一个typename(正如编译器所述)。但为什么?这感觉就像C语言中在非typedef类型前面缺少结构体一样。 - Ronny Brendel
@Ronny:那里需要使用typename关键字。typedef只是用来简化转换为通用容器(Tcontainer仍然被定义,但我切换了哪一个作为模板参数)。 - Ben Voigt
我猜测这行代码 "typedef container::value_type T; // if needed" 告诉编译器 container 是一个 vector、list 等容器类型,并确保 convert2Str 不能使用诸如 int 或 double 等简单数据类型进行调用。那么,为什么这条语句被标记为 "if needed" 呢?感谢快速回答。 - cppcoder
@srikrish:这行代码只会返回你原始代码中存在的“T”类型。虽然这个函数没有使用“T”,但我想向你展示如何访问它。即使没有这行代码,如果你试图传入一个不是容器的东西,编译器也会抱怨对“begin”和“end”的调用。 - Ben Voigt
那么如果T是一个用户定义的类型(我的自己的类),我能这样做吗sStream << T.getValue() << " "; ?? - cppcoder
@srikrish:不,你不能在类型后面使用 .。但是你可能想要声明一个类型为 T 的局部变量,比如 T sum = *it + 5; - Ben Voigt

6

按照STL的惯例,建议使用两个迭代器作为输入参数,而不是一个容器(因为很明显可以仅处理容器的一部分,并且通常适用于由迭代器定义的任何序列):

template<typename InputIterator>
std::string convert2Str(InputIterator first, InputIterator last)
{
    std::ostringstream sStream;
    for (InputIterator it = first; it != last; ++it) {
       sStream << *it << " ";
    }
    return sStream.str();
}

如果您需要包含对象的类型,请使用:
typedef typename std::iterator_traits<InputIterator>::value_type T;

新增: 然后您可以按以下方式使用该函数:

std::vector<int> int_vec;
std::list<float> f_list;
std::deque<std::string> str_deq;

     // put something into the containers here

std::cout<< convert2Str(int_vec.begin(), int_vec.end()) <<std::endl;
std::cout<< convert2Str(f_list.begin(), f_list.end()) <<std::endl;
std::cout<< convert2Str(str_deq.begin(), str_deq.end()) <<std::endl;

请注意,您无法迭代 std::queue;但如果您真的需要它,标准保证提供足够的支持来解决问题。在此处查看更多信息:std::queue 迭代

3

如果您只根据容器类型来创建模板,则最简单;所有标准、Boost和Qt容器中的值类型都存储在typedef成员value_type中。使用std::copyostream_iterator可跳过冗长的迭代器声明。

template <typename Container>
std::string convert2Str(Container const &cont)
{
    std::ostringstream s;
    std::copy(cont.begin(), cont.end(),
              std::ostream_iterator<typename Container::value_type>(s, " "));
    return s.str();
}

typename关键字是必要的,以避免歧义。最近的GCC版本会在省略此关键字时发出警告。


当然,这并没有真正教会srikrish如何编写自己的通用函数,这也是一项有用的技能。而ostream_iterator<decltype(*cont.begin())>将在C++0x中更好。 - Ben Voigt
@Ben:decltype很令人困惑;它看起来像是在运行/评估表达式,但实际上并不是。任何符合STL的容器都将具有value_type(标准容器、Boost容器甚至Qt中的容器都有)。 - Fred Foo
@larsman:抱歉,它确实应该是ostream_iterator<decltype(*begin(cont))>。原始数组没有value_type,但它们可以使用std::beginstd::end。而且绝对应该是using std::copy; copy(...);让Koenig查找发挥作用。请记住,函数模板不能部分特化,并且在namespace std中定义新的重载是被禁止的,为容器提供优化版本的唯一方法是使用ADL。 - Ben Voigt
这段代码能在像Solaris这样的平台上移植吗?最近我在我的代码中使用了std::count,但是在Solaris的sun编译器中出现了问题。你能解释一下这个语句"std::ostream_iterator<typename Container::value_type>(s, " ")"的作用吗?谢谢。 - cppcoder
@srikrish:它可以在任何符合标准的C++编译器上运行,但我不知道Sun编译器是否符合标准。请参见http://www.cplusplus.com/reference/std/iterator/ostream_iterator/。 - Fred Foo

2
使用这个。你需要 typename 部分来告诉编译器在解析时应该将 T::const_iterator 视为类型,直到你实际调用传递了具有 const_iterator 成员类型的某些 T ,编译器才能知道这是真实的。
template<typename T>
std::string convert2Str(T const& cont) 
{
    std::ostringstream sStream; 
    for (typename T::const_iterator it = cont.begin(); it != cont.end(); ++it) {
        sStream << *it << " "; 
    }
    return sStream.str(); 
}

typename 的确是个好点子。这正是尽快转向 C++0x 的又一个理由。 - Ben Voigt

0

我认为这应该可以工作:

template<typename T>
std::string convert2Str(T const& container) 
{
   std::ostringstream sStream; 
   for (typename T::const_iterator i= container.begin(); i != container.end(); ++i) {
      sStream << *i << " "; 
   }
   return sStream.str(); 
}

示例:http://ideone.com/9pUVV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接