为什么带有 initializer_list 参数的模板与字符串不一致?

Why a template with an initializer_list argument misbehaves with string?

C++ Most efficient way to compare a variable to multiple values? 之后,我正在尝试使用 initializer_list 作为参数构建一个模板函数。当我只使用字符串时就会出现问题。我有以下代码:

functions.hpp

template <typename T>
bool is_in(T const& val, initializer_list<T> const& liste)
{
   for (const auto& i : liste) 
      if (val == i) return true;
   return false;
};

main.cpp

#include "functions.hpp"
using namespace std;
int main()
{
   string test("hello");
   if (is_in(test, {"foo", "bar"}))
      cout << "good" << endl;
   else
      cout << "bad" << endl;
   return 0;
}

我收到以下错误:

main.cpp: In function ‘int main()’:
main.cpp:18:34: error: no matching function for call to ‘is_in(std::string&, <brace-enclosed initializer list>)’
main.cpp:18:34: note: candidate is:
In file included from personnage.hpp:11:0,
                 from main.cpp:1:
functions.hpp:16:6: note: template<class T> bool is_in(const T&, const std::initializer_list<_Tp>&)
functions.hpp:16:6: note:   template argument deduction/substitution failed:
main.cpp:18:34: note:   deduced conflicting types for parameter ‘_Tp’ (‘std::basic_string<char>’ and ‘const char*’)

我不明白的是:当我在 main 中使用 intdouble 而不是 string 时,一切都非常顺利...一个快速而肮脏的修复是将 is_in 声明为仅字符串的函数,但它不是很令人满意。

有谁知道如何保持模板和使用字符串一样吗?

答案很简单:

编译器不知道是否应该使用字符串中的 const char*std::string

如果你写

if (is_in(test, {std::string("foo"), std::string("bar")}))

它按预期工作。

这也是您的编译器给您的提示:

main.cpp:18:34: note: deduced conflicting types for parameter ‘_Tp’ (‘std::basic_string’ and ‘const char*’)

在你的函数模板中

template <typename T>
bool is_in(T const& val, initializer_list<T> const& liste)

两个参数都会参与template argument deduction,并且各自推导出的类型不同。 T 从第一个参数中扣除为 std::string,从第二个参数中扣除为 char const *(在模板参数推导期间不考虑用户定义的转换,因此从 char const *std::string 没有出现),这会导致编译错误。

有多种方法可以解决此问题。一种是在传递给 is_inbraced-init-list 中构造 strings,如果可以使用 C++,可以非常简洁地完成14 个 std::string_literals.

using namespace std::string_literals;

if (is_in(test, {"foo"s, "bar"s}))
//                    ^       ^

另一种方法是构造一个 initializer_list<string> 并将其传递给 is_in

if (is_in(test, initializer_list<string>{"foo", "bar"}))

你也可以重新定义is_in,使两种参数类型的模板类型参数不同,毕竟你不关心它们是否相同,你只需要它们可以通过operator==

template <typename T, typename U>
bool is_in(T const& val, initializer_list<U> const& liste)

另一种选择是防止其中一个函数参数参与模板参数推导。

template<typename T>
struct identity
{  using type = T;  };

template <typename T>
bool is_in(T const& val, initializer_list<typename identity<T>::type> const& liste)

第二个参数的T之上是一个non-deduced context,所以只有第一个参数参与模板实参推导


最后,is_in可以换成std::any_of

auto elems = {"foo", "bar"};
if (any_of(begin(elems), end(elems), [&](string const& s) { return s == test; }))

如果你想避免 lambda 表达式样板,你可以使用 boost::algorithm::any_of_equal.

auto elems = {"foo", "bar"};
if (boost::algorithm::any_of_equal(elems, test))