Following C++ Most efficient way to compare a variable to multiple values?, I am trying to build a template function with an initializer_list
as an argument. The problem arises when I use strings only. I have the following code :
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;
}
and I get the following error :
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*’)
The thing I don't understand is : when instead of string
I use int
or double
in my main
, everything goes perfectly well... A quick and dirty fix was to declare is_in
as a function of strings only, but it is not very satisfactory.
Does anyone know how to keep the template and use string all the same?
In your function template
template <typename T>
bool is_in(T const& val, initializer_list<T> const& liste)
both parameters will participate in template argument deduction, and the types deduced from each are different. T
is deducted as std::string
from the first parameter and as char const *
from the second (user defined conversions are not considered during template argument deduction, so the implicit conversion from char const *
to std::string
doesn't come into the picture), which leads to the compilation errors.
There are different ways to fix this problem. One would be to construct string
s within the braced-init-list you pass to is_in
, which can be done very succinctly if you can use C++14's std::string_literals
.
using namespace std::string_literals;
if (is_in(test, {"foo"s, "bar"s}))
// ^ ^
Another would be to construct an initializer_list<string>
and pass that to is_in
if (is_in(test, initializer_list<string>{"foo", "bar"}))
You could also redefine is_in
so that the template type parameters for the two parameter types are distinct, after all, you don't care about them being the same, you just need them to comparable by operator==
template <typename T, typename U>
bool is_in(T const& val, initializer_list<U> const& liste)
Yet another option is to prevent one of the function parameters from participating in template argument deduction.
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)
Above the T
in the second parameter is a non-deduced context, so only the first parameter takes part in template argument deduction.
Finally, is_in
can be replaced by std::any_of
.
auto elems = {"foo", "bar"};
if (any_of(begin(elems), end(elems), [&](string const& s) { return s == test; }))
If you want to avoid the lambda expression boilerplate, you can use boost::algorithm::any_of_equal
.
auto elems = {"foo", "bar"};
if (boost::algorithm::any_of_equal(elems, test))