c++ 模板类;具有任意容器类型的函数,如何定义它?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7728478/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
c++ template class; function with arbitrary container type, how to define it?
提问by Fault
Okay, simple template question. Say I define my template class something like this:
好的,简单的模板问题。假设我定义我的模板类是这样的:
template<typename T>
class foo {
public:
foo(T const& first, T const& second) : first(first), second(second) {}
template<typename C>
void bar(C& container, T const& baz) {
//...
}
private:
T first;
T second;
}
The question is about my bar function... I need it to be able to use a standard container of some sort, which is why I included the template/typename C part, to define that container type. But apparently that's not the right way to do it, since my test class then complains that:
问题是关于我的 bar 函数......我需要它能够使用某种标准容器,这就是为什么我包含模板/类型名称 C 部分来定义该容器类型。但显然这不是正确的方法,因为我的测试班然后抱怨:
error: 'bar' was not declared in this scope
错误:“bar”未在此范围内声明
So how would I go about implementing my bar function the proper way? That is, as a function of my template class, with an arbitrary container type... the rest of my template class works fine (has other functions that don't result in an error), it's just that one function that's problematic.
那么我将如何以正确的方式实现我的 bar 功能呢?也就是说,作为我的模板类的一个函数,具有任意容器类型......我的模板类的其余部分工作正常(具有不会导致错误的其他函数),这只是一个有问题的函数。
EDIT: Okay, so the specific function (bar) is an eraseInRange function, that erases all elements in a specified range:
编辑:好的,所以特定函数 (bar) 是一个 eraseInRange 函数,它擦除指定范围内的所有元素:
void eraseInRange(C& container, T const& firstElement, T const& secondElement) {...}
And an example of how it would be used would be:
如何使用它的一个例子是:
eraseInRange(v, 7, 19);
where v is a vector in this case.
在这种情况下,v 是一个向量。
EDIT 2: Silly me! I was supposed to declare the function outside of my class, not in it... pretty frustrating mistake to be making. Anyways, thanks everyone for the help, though the problem was a little different, the information did help me construct the function, since after finding my original problem, I did get some other pleasant errors. So thank you!
编辑2:傻我!我应该在我的班级之外声明这个函数,而不是在里面……这是一个非常令人沮丧的错误。无论如何,感谢大家的帮助,虽然问题有点不同,但这些信息确实帮助我构建了函数,因为在找到我原来的问题后,我确实遇到了一些其他令人愉快的错误。所以谢谢!
回答by Sebastian Mach
Traits solution.
性状解决。
Generalize not more than needed, and not less.
概括不要超过需要,也不要少于需要。
In some cases that solution might not be enough as it will match any template with such signature (e.g. shared_ptr
), in which case you could make use of type_traits
, very much like duck-typing(templates are duck typed in general).
在某些情况下,该解决方案可能还不够,因为它会匹配具有此类签名的任何模板(例如shared_ptr
),在这种情况下,您可以使用type_traits
,非常类似于duck-typing(模板通常是duck 类型)。
#include <type_traits>
// Helper to determine whether there's a const_iterator for T.
template<typename T>
struct has_const_iterator
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
// bar() is defined for Containers that define const_iterator as well
// as value_type.
template <typename Container>
typename std::enable_if<has_const_iterator<Container>::value,
void>::type
bar(const Container &c, typename Container::value_type const & t)
{
// Note: no extra check needed for value_type, the check comes for
// free in the function signature already.
}
template <typename T>
class DoesNotHaveConstIterator {};
#include <vector>
int main () {
std::vector<float> c;
bar (c, 1.2f);
DoesNotHaveConstIterator<float> b;
bar (b, 1.2f); // correctly fails to compile
}
A good template usually does notartificially restrict the kind of types for which they are valid (why should they?). But imagine in the example above you need to have access to an objects const_iterator
, then you can use SFINAE and type_traits to put those constraints on your function.
一个好的模板通常并不会人为限制的那种这是他们的有效类型(他们为什么要?)。但是想象一下在上面的例子中你需要访问一个 objects const_iterator
,然后你可以使用 SFINAE 和 type_traits 将这些约束放在你的函数上。
Or just to as the standard library does
或者就像标准库一样
Generalize not more than needed, and not less.
概括不要超过需要,也不要少于需要。
template <typename Iter>
void bar (Iter it, Iter end) {
for (; it!=end; ++it) { /*...*/ }
}
#include <vector>
int main () {
std::vector<float> c;
bar (c.begin(), c.end());
}
For more such examples, look into <algorithm>
.
有关更多此类示例,请查看<algorithm>
.
This approach's strength is its simplicity and is based on concepts like ForwardIterator. It will even work for arrays. If you want to report errors right in the signature, you can combine it with traits.
这种方法的优势在于它的简单性,并且基于ForwardIterator 之类的概念。它甚至适用于数组。如果你想直接在签名中报告错误,你可以将它与特征结合起来。
std
containers with signature like std::vector
(not recommended)
std
带有签名的容器std::vector
(不推荐)
The simplest solution is approximated by Kerrek SB already, though it is invalid C++. The corrected variant goes like so:
Kerrek SB 已经近似了最简单的解决方案,尽管它是无效的 C++。修正后的变体是这样的:
#include <memory> // for std::allocator
template <template <typename, typename> class Container,
typename Value,
typename Allocator=std::allocator<Value> >
void bar(const Container<Value, Allocator> & c, const Value & t)
{
//
}
However: this will only work for containers that have exactly two template type arguments, so will fail miserably for std::map
(thanks Luc Danton).
但是:这仅适用于恰好具有两个模板类型参数的容器,因此会失败std::map
(感谢 Luc Danton)。
Any kind of secondary template arguments (not recommended)
任何类型的辅助模板参数(不推荐)
The corrected version for any secondary parameter count is as follows:
任何次要参数计数的更正版本如下:
#include <memory> // for std::allocator<>
template <template <typename, typename...> class Container,
typename Value,
typename... AddParams >
void bar(const Container<Value, AddParams...> & c, const Value & t)
{
//
}
template <typename T>
class OneParameterVector {};
#include <vector>
int main () {
OneParameterVector<float> b;
bar (b, 1.2f);
std::vector<float> c;
bar (c, 1.2f);
}
However: this will still fail for non-template containers (thanks Luc Danton).
但是:对于非模板容器,这仍然会失败(感谢 Luc Danton)。
回答by Kerrek SB
Make the template templated on a template template parameter:
在模板模板参数上制作模板:
template <template <typename, typename...> class Container>
void bar(const Container<T> & c, const T & t)
{
//
}
If you don't have C++11, then you can't use variadic templates, and you have to provide as many template parameters as your container takes. For example, for a sequence container you might need two:
如果您没有 C++11,则无法使用可变参数模板,并且您必须提供与容器所需的模板参数一样多的模板参数。例如,对于序列容器,您可能需要两个:
template <template <typename, typename> class Container, typename Alloc>
void bar(const Container<T, Alloc> & c, const T & t);
Or, if you only want to allow allocators which are themselves template instances:
或者,如果您只想允许本身是模板实例的分配器:
template <template <typename, typename> class Container, template <typename> class Alloc>
void bar(const Container<T, Alloc<T> > & c, const T & t);
As I suggested in the comments, I would personally prefer to make the entire container a templated typeand use traits to check if it's valid. Something like this:
正如我在评论中所建议的那样,我个人更喜欢将整个容器设为模板类型并使用特征来检查它是否有效。像这样的东西:
template <typename Container>
typename std::enable_if<std::is_same<typename Container::value_type, T>::value, void>::type
bar(const Container & c, const T & t);
This is more flexible since the container can now be anything that exposes the value_type
member type. More sophisticated traits for checking for member functions and iterators can be conceived of; for example, the pretty printerimplements a few of those.
这更加灵活,因为容器现在可以是任何公开value_type
成员类型的东西。可以设想用于检查成员函数和迭代器的更复杂的特征;例如,漂亮的打印机实现了其中的一些。
回答by Shital Shah
Here's the latest and expanded version of this answerand significant improvement over answer by Sabastian.
这是此答案的最新和扩展版本,以及对 Sabastian 的回答的重大改进。
The idea is to define all traits of STL containers. Unfortunately, this gets tricky very fast and fortunately lot of people have worked on tuning this code. These traits are reusable so just copy and past below code in file called type_utils.hpp (feel free to change these names):
这个想法是定义 STL 容器的所有特征。不幸的是,这很快就会变得棘手,幸运的是,很多人都在努力调整这段代码。这些特征是可重用的,因此只需复制并传递名为 type_utils.hpp 的文件中的以下代码(随意更改这些名称):
//put this in type_utils.hpp
#ifndef commn_utils_type_utils_hpp
#define commn_utils_type_utils_hpp
#include <type_traits>
#include <valarray>
namespace common_utils { namespace type_utils {
//from: https://raw.githubusercontent.com/louisdx/cxx-prettyprint/master/prettyprint.hpp
//also see https://gist.github.com/louisdx/1076849
namespace detail
{
// SFINAE type trait to detect whether T::const_iterator exists.
struct sfinae_base
{
using yes = char;
using no = yes[2];
};
template <typename T>
struct has_const_iterator : private sfinae_base
{
private:
template <typename C> static yes & test(typename C::const_iterator*);
template <typename C> static no & test(...);
public:
static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
using type = T;
void dummy(); //for GCC to supress -Wctor-dtor-privacy
};
template <typename T>
struct has_begin_end : private sfinae_base
{
private:
template <typename C>
static yes & f(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::begin)),
typename C::const_iterator(C::*)() const>::value>::type *);
template <typename C> static no & f(...);
template <typename C>
static yes & g(typename std::enable_if<
std::is_same<decltype(static_cast<typename C::const_iterator(C::*)() const>(&C::end)),
typename C::const_iterator(C::*)() const>::value, void>::type*);
template <typename C> static no & g(...);
public:
static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
void dummy(); //for GCC to supress -Wctor-dtor-privacy
};
} // namespace detail
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template <typename T>
struct is_container : public std::integral_constant<bool,
detail::has_const_iterator<T>::value &&
detail::has_begin_end<T>::beg_value &&
detail::has_begin_end<T>::end_value> { };
template <typename T, std::size_t N>
struct is_container<T[N]> : std::true_type { };
template <std::size_t N>
struct is_container<char[N]> : std::false_type { };
template <typename T>
struct is_container<std::valarray<T>> : std::true_type { };
template <typename T1, typename T2>
struct is_container<std::pair<T1, T2>> : std::true_type { };
template <typename ...Args>
struct is_container<std::tuple<Args...>> : std::true_type { };
}} //namespace
#endif
Now you can use these traits to make sure our code only accepts container types. For example, you can implement append function that appends one vector to another like this:
现在您可以使用这些特征来确保我们的代码只接受容器类型。例如,您可以实现将一个向量附加到另一个向量的 append 函数,如下所示:
#include "type_utils.hpp"
template<typename Container>
static typename std::enable_if<type_utils::is_container<Container>::value, void>::type
append(Container& to, const Container& from)
{
using std::begin;
using std::end;
to.insert(end(to), begin(from), end(from));
}
Notice that I'm using begin() and end() from std namespace just to be sure we have iterator behavior. For more explanation see my blog post.
请注意,我使用 std 命名空间中的 begin() 和 end() 只是为了确保我们具有迭代器行为。有关更多解释,请参阅我的博客文章。