(C++) 检查对象是否在 vector/array/list/... 中的模板?
(C++) Template to check whether an object is in a vector/array/list/...?
是否可以在 C++(11) 中为函数创建一个模板来检查对象是否包含在 std::vector
、std::array
或 std::list
中(并且可能更多容器类型)?
我现在拥有的:
typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;
bool
Tag::isIn(const TagList& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
}
Tag
是正常的 class
。当然,比较应该是t == this
,稍后会是operator==
。为简单起见,我没有在此处包含此内容。
那么,是否可以只为 std::vector
、std::array
、std::list
(,可能为 std::set
) 等等?
我找不到所有这些的基类型 类,...这将是我的第一个想法...
您可以使用模板:
typedef std::shared_ptr<Tag> SharedTag;
template <typename Container>
bool Tag::isIn(const Container& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
}
这要求 Container 是可转换为 SharedTag
的容器。
方案一(好):直接使用std::find
即可:
std::vector<int> v; // populate v however you want
std::vector<int>::const_iterator i = std::find(v.cbegin(), v.cend(), 42);
if (i != v.end()) {
// Now you know 42 is in v
} else {
// Now you know 42 is not in v
}
选项 2(更好):将 std::find
包装在辅助函数中:
template <typename Container, typename Value>
bool contains(const Container& c, const Value& v)
{
return std::find(std::begin(c), std::end(c), v) != std::begin(c);
}
// Example usage:
std::vector<int> v; // populate v however you want
if (contains(v, 42)) {
// You now know v contains 42
}
选项 3(最佳):使用提供一个容器的 find
方法(这对于排序容器更快,例如 set
),对于不提供容器的容器使用 std::find
'提供一个:
// If you want to know why I added the int and long parameter,
// see this answer here:
template <typename Container, typename Value>
inline auto contains(const Container& c, const Value& v, int) -> decltype(c.find(v), bool()) {
return c.find(v) != std::end(c);
}
template <typename Container, typename Value>
inline bool contains(const Container& c, const Value& v, long) {
return std::find(std::begin(c), std::end(c), v) != std::end(c);
}
template <typename Container, typename Value>
bool contains(const Container& c, const Value& v) {
return contains(c, v, 0);
}
// Example usage:
std::set<int> s; // populate s however you want
if (contains(s, 42)) {
// You now know s contains 42
}
当然,你可以自己写std::find
,但你也可以使用它。
这些容器之间没有共同的基类型。这不是 STL 库的工作方式,它基于模板和通用编程原则。
因此,如果您想为所有容器实现一次该功能,则必须将其设为模板。这是一个基本形式:
template <typename TagContainer>
bool Tag::isIn(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
但这有一个问题,你可以在技术上将任何实际上不是 SharedTag
容器的东西传递给这个函数,所以,要解决这个问题,你可以使用一个叫做 Sfinae 的技巧来强制执行规则:
template <typename TagContainer>
typename std::enable_if< std::is_same< SharedTag, typename TagContainer::value_type >::value,
bool >::type Tag::isIn(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
虽然有点丑,但是很管用。
不过还有一个问题。我怀疑你的Tag
class是一个普通的非模板class,这意味着你很可能是在cpp文件中实现它,但是模板需要在头文件中实现(因为函数模板需要让它们的实现对编译器可见,以便为您调用它的每种类型生成一个新的具体版本。
避免这个问题的一种方法是为每个你想支持的容器提供一些重载的非模板函数,然后在后台调用一个本地函数模板,在这种情况下,您不需要 sfinae 技巧来约束它,因为它已经限于您提供的重载集。像这样:
template <typename TagContainer>
bool Tag::isIn_impl(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
bool Tag::isIn(const std::list<SharedTag>& lst) {
return isIn_impl(lst);
};
bool Tag::isIn(const std::vector<SharedTag>& lst) {
return isIn_impl(lst);
};
bool Tag::isIn(const std::set<SharedTag>& lst) {
return isIn_impl(lst);
};
请注意,isIn_impl
是一个成员函数模板,应在头文件 class 的私有部分中 声明 ,并且可以安全地在 cpp 文件中 定义,因为该 cpp 文件是唯一调用该函数模板的地方。
该解决方案的明显问题是您必须手动提供您想要支持的每个重载,这意味着它在未来不是很 "scalable",但在现实生活中,有可能没有那么多您想要支持的容器。如果你想要全面的通用性,你真的必须使用模板方法(除非你想在容器上进行类型擦除......但这有点超出了我愿意在这里解释的范围)。
您可以使用嵌套可变参数模板来实现此目的。这是一个方便的演示:注意神奇的部分,template <template <typename...> class V, typename E>
。可变参数模板是必需的,因为 vector
、list
&co。都有不同数量的模板参数(分配器、比较器等),STL 为其提供默认值。
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <list>
#include <set>
#include <iostream>
class Tag {
public:
Tag(const std::string &n): name(n) {}
template <template <typename...> class V, typename E>
bool isIn(const V<E> &lst) {
return std::any_of(lst.begin(), lst.end(), [this](const E &t) {
return t.name == this->name;
});
}
private:
std::string name;
};
typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;
int main() {
Tag t("foo");
// Set needs some extra bits to work (a `<` operator etc.)
//std::set<Tag> a = {Tag("foo"), Tag("bar")};
std::vector<Tag> b = {Tag("foo"), Tag("bar")};
std::list<Tag> c = {Tag("foo"), Tag("bar")};
//std::cout << t.isIn(a) << std::endl;
std::cout << t.isIn(b) << std::endl;
std::cout << t.isIn(c) << std::endl;
}
是否可以在 C++(11) 中为函数创建一个模板来检查对象是否包含在 std::vector
、std::array
或 std::list
中(并且可能更多容器类型)?
我现在拥有的:
typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;
bool
Tag::isIn(const TagList& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
}
Tag
是正常的 class
。当然,比较应该是t == this
,稍后会是operator==
。为简单起见,我没有在此处包含此内容。
那么,是否可以只为 std::vector
、std::array
、std::list
(,可能为 std::set
) 等等?
我找不到所有这些的基类型 类,...这将是我的第一个想法...
您可以使用模板:
typedef std::shared_ptr<Tag> SharedTag;
template <typename Container>
bool Tag::isIn(const Container& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
}
这要求 Container 是可转换为 SharedTag
的容器。
方案一(好):直接使用std::find
即可:
std::vector<int> v; // populate v however you want
std::vector<int>::const_iterator i = std::find(v.cbegin(), v.cend(), 42);
if (i != v.end()) {
// Now you know 42 is in v
} else {
// Now you know 42 is not in v
}
选项 2(更好):将 std::find
包装在辅助函数中:
template <typename Container, typename Value>
bool contains(const Container& c, const Value& v)
{
return std::find(std::begin(c), std::end(c), v) != std::begin(c);
}
// Example usage:
std::vector<int> v; // populate v however you want
if (contains(v, 42)) {
// You now know v contains 42
}
选项 3(最佳):使用提供一个容器的 find
方法(这对于排序容器更快,例如 set
),对于不提供容器的容器使用 std::find
'提供一个:
// If you want to know why I added the int and long parameter,
// see this answer here:
template <typename Container, typename Value>
inline auto contains(const Container& c, const Value& v, int) -> decltype(c.find(v), bool()) {
return c.find(v) != std::end(c);
}
template <typename Container, typename Value>
inline bool contains(const Container& c, const Value& v, long) {
return std::find(std::begin(c), std::end(c), v) != std::end(c);
}
template <typename Container, typename Value>
bool contains(const Container& c, const Value& v) {
return contains(c, v, 0);
}
// Example usage:
std::set<int> s; // populate s however you want
if (contains(s, 42)) {
// You now know s contains 42
}
当然,你可以自己写std::find
,但你也可以使用它。
这些容器之间没有共同的基类型。这不是 STL 库的工作方式,它基于模板和通用编程原则。
因此,如果您想为所有容器实现一次该功能,则必须将其设为模板。这是一个基本形式:
template <typename TagContainer>
bool Tag::isIn(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
但这有一个问题,你可以在技术上将任何实际上不是 SharedTag
容器的东西传递给这个函数,所以,要解决这个问题,你可以使用一个叫做 Sfinae 的技巧来强制执行规则:
template <typename TagContainer>
typename std::enable_if< std::is_same< SharedTag, typename TagContainer::value_type >::value,
bool >::type Tag::isIn(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
虽然有点丑,但是很管用。
不过还有一个问题。我怀疑你的Tag
class是一个普通的非模板class,这意味着你很可能是在cpp文件中实现它,但是模板需要在头文件中实现(因为函数模板需要让它们的实现对编译器可见,以便为您调用它的每种类型生成一个新的具体版本。
避免这个问题的一种方法是为每个你想支持的容器提供一些重载的非模板函数,然后在后台调用一个本地函数模板,在这种情况下,您不需要 sfinae 技巧来约束它,因为它已经限于您提供的重载集。像这样:
template <typename TagContainer>
bool Tag::isIn_impl(const TagContainer& lst) {
return std::any_of(lst.begin(), lst.end(), [this](const SharedTag& t) {
return t->name == this->name;
});
};
bool Tag::isIn(const std::list<SharedTag>& lst) {
return isIn_impl(lst);
};
bool Tag::isIn(const std::vector<SharedTag>& lst) {
return isIn_impl(lst);
};
bool Tag::isIn(const std::set<SharedTag>& lst) {
return isIn_impl(lst);
};
请注意,isIn_impl
是一个成员函数模板,应在头文件 class 的私有部分中 声明 ,并且可以安全地在 cpp 文件中 定义,因为该 cpp 文件是唯一调用该函数模板的地方。
该解决方案的明显问题是您必须手动提供您想要支持的每个重载,这意味着它在未来不是很 "scalable",但在现实生活中,有可能没有那么多您想要支持的容器。如果你想要全面的通用性,你真的必须使用模板方法(除非你想在容器上进行类型擦除......但这有点超出了我愿意在这里解释的范围)。
您可以使用嵌套可变参数模板来实现此目的。这是一个方便的演示:注意神奇的部分,template <template <typename...> class V, typename E>
。可变参数模板是必需的,因为 vector
、list
&co。都有不同数量的模板参数(分配器、比较器等),STL 为其提供默认值。
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <list>
#include <set>
#include <iostream>
class Tag {
public:
Tag(const std::string &n): name(n) {}
template <template <typename...> class V, typename E>
bool isIn(const V<E> &lst) {
return std::any_of(lst.begin(), lst.end(), [this](const E &t) {
return t.name == this->name;
});
}
private:
std::string name;
};
typedef std::shared_ptr<Tag> SharedTag;
typedef std::vector<SharedTag> TagList;
int main() {
Tag t("foo");
// Set needs some extra bits to work (a `<` operator etc.)
//std::set<Tag> a = {Tag("foo"), Tag("bar")};
std::vector<Tag> b = {Tag("foo"), Tag("bar")};
std::list<Tag> c = {Tag("foo"), Tag("bar")};
//std::cout << t.isIn(a) << std::endl;
std::cout << t.isIn(b) << std::endl;
std::cout << t.isIn(c) << std::endl;
}