如何在C ++模板参数

时间:2015-05-23 23:24:41

标签: c++

我想编写一个接受类型为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

我该如何解决这个问题?我对多个答案感兴趣,因为每个解决方案可能都有优点和缺点。

3 个答案:

答案 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_ifstd::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:更加丑陋,更复杂的代码。