Emil Ahlbäck
Emil Ahlbäck

Reputation: 6206

Detecting type inclusion in std::tuple at compile time

My goal is to write a function that:

  1. is callable at compile time
  2. takes a template argument Tuple that represents an arbitrary std::tuple
  3. takes a template argument Match that represents the type to test for
  4. returns true if the tuple has 1+ element of type Match

I'm on GCC 10.2, which I can upgrade if needed. I could also switch to Clang. I'm avoiding Boost, but also starting to feel like its inclusion in my project is inevitable.

If C++20 would allow the idx variable as a compile-time constant, I would be done. However, that didn't make it into the standard:

template<typename Tuple, typename Match>
consteval bool tuple_holds()
{
    constexpr std::size_t size = std::tuple_size<Tuple>::value;
    constexpr auto all = std::ranges::iota_view{std::size_t{0}, size};

    for (const auto idx : all) {
        // This doesn't work, as idx is not available as a compile-time constant
        using Type = std::tuple_element<idx, Tuple>::type;

        if (std::is_same<Type, Match>::value) {
            return true;
        }
    }

    return false;
}


using A = std::tuple<float, int>;


int main()
{
    static_assert(tuple_holds<A, int>());
    static_assert(tuple_holds<A, float>());
}

I suspect the answer lies in SFINAE, but I'm unsure of how to get further.

Upvotes: 3

Views: 1118

Answers (4)

cigien
cigien

Reputation: 60218

Here's another solution that is pretty similar to @HolyBlackCat's answer, but uses std::disjunction instead of a fold-expression, and can be used the same way you have in your question (it doesn't require ::value for the usage):

template<typename, typename>
struct tuple_holds;

template<typename ...Ts, typename T>
struct tuple_holds<std::tuple<Ts...>, T> 
  : std::disjunction<std::is_same<Ts, T>...> {};

Here's a demo.

Upvotes: 2

Barry
Barry

Reputation: 302922

With Boost.Mp11, this is a short one-line (as always):

template <typename Tuple, typename Match>
static constexpr bool tuple_holds = mp_contains<Tuple, Match>::value;

This doesn't work:

for (const auto idx : all) {
    using Type = std::tuple_element<idx, Tuple>::type;

    if (std::is_same<Type, Match>::value) {

Because you need idx to be a constant expression in order to be usable as a template argument, and idx is not a constant expression. You need to have something like a constexpr variable for this. This was one of the motivations for the expansion statements language feature - which would've allowed you to write:

template for (constexpr auto idx : all)

Allowing you to use idx as a template argument - by basically instantiating the body of the for loop on each iteration. But we don't have that yet.

Upvotes: 4

cdhowie
cdhowie

Reputation: 169008

For a pre-C++17 solution not dependent on fold expressions, you can use a simple recursive template.

// Base: Provide no constant when first argument is not std::tuple<...>
template <typename, typename>
struct tuple_holds {};

// Termination: true
template <typename T, typename... Args>
struct tuple_holds<std::tuple<T, Args...>, T> : std::true_type {};

// Termination: false
template <typename T>
struct tuple_holds<std::tuple<>, T> : std::false_type {};

// Recursive
template <typename T, typename THead, typename... TTail>
struct tuple_holds<std::tuple<THead, TTail...>, T> : tuple_holds<std::tuple<TTail...>, T> {};

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96166

You don't need fancy C++20 features to do it.

template <typename, typename>
struct tuple_holds {};

template <typename ...A, typename B>
struct tuple_holds<std::tuple<A...>, B>
    : std::bool_constant<(std::is_same_v<A, B> || ...)>
{};

Usage: tuple_holds<std::tuple<A, B>, C>::value.

Upvotes: 8

Related Questions