我想编写一个接受类型为T
的集合的函数,比如std::vector<T>
,但根据T
执行两项不同的操作。例如,如果T
与==
具有可比性,则使用a == b
,否则如果T
具有.value
元素,请使用a.value == b.value
我的第一次尝试是使用重载函数,但如果我传入T
的派生类(子类),则会失败。
例如,假设我想创建一个Exists
方法。 (我知道这可以使用std::find_if
来实现;它只是一个例子。)以下无法编译:
using namespace std;
struct Base {
Base(string s) : value(std::move(s)) {}
string value;
};
struct Derived : public Base {
Derived(string s) : Base(std::move(s)) {}
};
bool Exists(const vector<string>& collection, const string& item) {
for (const auto& x : collection)
if (x == item)
return true;
return false;
}
bool Exists(const vector<Base>& collection, const Base& item) {
for (const auto& x : collection)
if (x.value == item.value)
return true;
return false;
}
这适用于完全匹配,例如:
Exists(vector<string>{"a", "b", "c"}, "b");
Exists(vector<Base>{{"a"}, {"b"}, {"c"}}, Base{"b"});
但派生类失败了:
Exists(vector<Derived>{{"a"}, {"b"}, {"c"}}, Derived{"b"})
错误是:
foo.cc:35:13: error: no matching function for call to 'Exists'
foo.cc:23:6: note: candidate function not viable: no known conversion from 'vector<Derived>' to 'const vector<Base>' for
1st argument
我该如何解决这个问题?我对多个答案感兴趣,因为每个解决方案可能都有优点和缺点。
答案 0 :(得分:2)
这本身可能不是重复,但非常接近于此: Is it possible to write a template to check for a function's existence?
我推荐的方法是在该答案中实现的更通用的解决方案:使用SFINAE。
如下测试成员函数的片段(改编自here):
template <class T>
class has_value {
template <class M>
static inline bool try_match(decltype(&M::value)) { }
template <class M>
static inline int try_match(...) { }
public:
static constexpr bool value =
sizeof(try_match<T>(nullptr)) == sizeof(bool);
};
然后可以将其与std::enable_if
结合使用以解决您的问题。我已发布完整的解决方案as a GitHub gist。
在我看来,这优于使用基本和继承检查,因为它通过简单地检查(在编译时)给定类型是否具有给定成员来工作。此外,它适用于具有类型的任何内容,包括成员,函数,静态成员/函数,类型等。
答案 1 :(得分:1)
一种解决方案是模板化Exists()
方法,然后重载比较函数。这仅适用于可以隔离特定于类型的代码的情况。例如:
bool Equals(const string& a, const string& b) { return a == b; }
bool Equals(const Base& a, const Base& b) { return a.value == b.value; }
template <typename T>
bool Exists(const vector<T>& collection,
const typename vector<T>::value_type& item) {
for (const auto& x : collection)
if (Equals(x, item))
return true;
return false;
}
Pro:可能是最简单的解决方案。
Con:如果你需要预先做一些昂贵的工作,那就不行了。例如,如果您需要调用x.SomeExpensiveMethod()
并且想要为item
参数缓存它,则无效。
请注意,您需要在参数中使用vector<t>::value_type
而不仅仅是T
,否则您可能会收到如下错误:
foo3.cc:30:13: error: no matching function for call to 'Exists'
cout << Exists(vector<string>{"a", "b", "c"}, "b") << endl;
^~~~~~
foo3.cc:21:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('std::basic_string<char>' vs.
'char [2]')
答案 2 :(得分:0)
一种解决方案是使用std::enable_if
和std::is_base_of
。例如:
template <typename T>
typename std::enable_if<std::is_base_of<Base, T>::value, bool>::type
Exists(const vector<T>& collection,
const typename vector<T>::value_type& item) {
const auto& item_cached = item.SomeExpensiveFunction();
for (const auto& x : collection)
if (x.SomeExpensiveFunction() == item_cached)
return true;
return false;
}
template <typename T>
typename std::enable_if<!std::is_base_of<Base, T>::value, bool>::type
Exists(const vector<T>& collection,
const typename vector<T>::value_type& item) {
for (const auto& x : collection)
if (x == item)
return true;
return false;
}
Pro:比重载Equals()
功能更常见,如另一个答案中所述。特别是,可以按类型自定义整个Exists()
方法。
Con:更加丑陋,更复杂的代码。