3

I would like to do the following:

std::string b = "b";
is_in("a", { "a", "b", "c" });
is_in("d", { "a", "b", "c" });
is_in(b, { "a", "b", "c" }); // fails
is_in(b, std::array{ "a", "b", "c" });

using the templates

template<typename Element, typename Container>
bool is_in(const Element& e, const Container& c)
{
    // https://stackoverflow.com/questions/20303821/how-to-check-if-string-is-in-array-of-strings
    return std::find(std::begin(c), std::end(c), e) != std::end(c);
}

template<typename Element>
bool is_in(Element e, std::initializer_list<Element> l)
{
    // return std::find(std::begin(l), std::end(l), e) != std::end(l);
    return is_in<Element, std::initializer_list<Element>>(e, l);
}

but I get the following error (using GCC 9.3.0):

no matching function for call to ‘is_in(std::string&, <brace-enclosed initializer list>)’

Any big brain template fellas out there got suggestions?

0

3 Answers 3

7

For is_in(b, { "a", "b", "c" });, template parameter Element is deduced as std::string on the 1st argument b, and deduced as const char* on the 2nd argument { "a", "b", "c" }; they don't match.

You can give two template parameters for is_in, e.g.

template<typename E1, typename E2>
bool is_in(E1 e, std::initializer_list<E2> l)
{
    // return std::find(std::begin(l), std::end(l), e) != std::end(l);
    return is_in<E1, std::initializer_list<E2>>(e, l);
}

Or use std::type_identity (since C++20; and it's quite easy to write one for pre-C++20) to exclude the 2nd function parameter from type deduction.

template<typename Element>
bool is_in(Element e, std::initializer_list<std::type_identity_t<Element>> l)
{
    // return std::find(std::begin(l), std::end(l), e) != std::end(l);
    return is_in<Element, std::initializer_list<Element>>(e, l);
}
Sign up to request clarification or add additional context in comments.

3 Comments

Oh, that makes perfect sense, thanks a bunch! Can you link me how to rewrite std::type_identity?
@Androvich: link provides possible implementation.
1

Another approach that involves converting unmatching string types to std::string before comparing them.

#include <cassert>
#include <array>
#include <string>

// Help the compiler figure out to compare "unrelated" string types
namespace details
{
    template<typename type_t>
    struct compare_as
    {
        using type = type_t;
    };

    template<std::size_t N>
    struct compare_as<char[N]>
    {
        using type = std::string;
    };

    template<>
    struct compare_as<char*>
    {
        using type = std::string;
    };
}

// template for "array" style parameters 
template<typename type_t, typename coll_t, std::size_t N>
constexpr auto is_in(const type_t& value, const coll_t(&values)[N])
{
    for (const auto& v : values)
    {
        typename details::compare_as<coll_t>::type lhs{ v };
        typename details::compare_as<type_t>::type rhs{ value };
        if (lhs == rhs) return true;
    }

    return false;
}

// template for containers
template<typename type_t, typename coll_t>
constexpr auto is_in(const type_t& value, const coll_t& values)
{
    for (const auto& v : values)
    {
        typename details::compare_as<type_t>::type lhs{ v };
        typename details::compare_as<type_t>::type rhs{ value };
        if (lhs == rhs) return true;
    }

    return false;
}

int main()
{
    // for non-string types compile time checking is possible
    static_assert(is_in(1, { 1,2,3 }));

    std::string b = "b";
    assert(is_in("a", { "a", "b", "c" }));
    assert(!is_in("d", { "a", "b", "c" }));
    assert(is_in(b, { "a", "b", "c" }));
    assert(is_in(b, std::array{ "a", "b", "c" }));
}

2 Comments

Oh, that's pretty neat. But wouldn't it be better to keep it more generic, such that it can be used with primitives and std::string? Or any type that std::find works with, actually.
Probably yes :) As said this was just a few minutes worth of effort. And most types are actually comparable it is just that arrays of characters (string literals) in c++ are so messy.
0

From C++17, you can do something similar with variadic template and fold expressions :

template <typename T, typename... Ts>
bool is_in(T const& x, Ts const&... y)
{
  return ((x == y) ||...);
}

And call it :

is_in("a", "a", "b", "c");

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.