Can C++ tuple element types be conditionally added based on template parameters?
Asked Answered
H

3

14

I'm trying to conditionally add types to a tuple template type based on some compile time condition, as follows:

template <typename T>
class Base { ... }

template <int I>
class Derived : public Base<std::tuple<std::conditional<I % 8 == 0, int,    void>::type,
                                       std::conditional<I % 4 == 0, double, void>::type,
                                       std::conditional<I % 2 == 0, float,  void>::type>>
{ ... }

I understand that this isn't valid code, but conceptually I'm trying to conditionally add types to the tuple's list. I would like the type to be skipped when the conditional resolves to void.

Is there a way to do something like this?

Hardness answered 19/4, 2021 at 1:45 Comment(0)
N
12

A small part of your question does not compute:

std::conditional<I % 1 == 0, float,  void>:

Since the remainder of any integer when divided by 1 is 0, this conditional will always be true.

Whatever the actual intent was here, I'll just give an example with two conditionals that can be trivially extended to include additional conditionals, as needed:

#include <tuple>
#include <functional>

template <typename T>
class Base {};

template <int I>
class Derived : public Base<
    decltype(std::tuple_cat(
             std::declval<
                std::conditional_t<I % 4 == 0,
                                  std::tuple<int>,
                                  std::tuple<>>>(),
             std::declval<
                std::conditional_t<I % 2 == 0,
                                  std::tuple<float>,
                                  std::tuple<>>>()
             ))>
{
};

Derived<4> a;

Base<std::tuple<int, float>> &b=a;

Derived<2> c;

Base<std::tuple<float>> &d=c;

This is taking advantage of the fact that std::tuple_cat is perfectly happy with std::tuple<>, and we just need to (ab)use it to glue the right combination of tuples together.

Neau answered 19/4, 2021 at 2:0 Comment(1)
I wasn't aware of std::tuple_cat, but it's just the trick needed here - thanks! And you're right regarding the %, I meant 2/4/8.Hardness
M
0
template <class..Ts>
struct types {
  template <template <class...> class Z>
  using apply = Z<Ts...>;
  template<class...Us>
  constexpr types <Ts...,Us...> operator+(types<Us...>)const{return {};}
};

now

template <unsigned int I>
class Derived : public Base<decltype(
                                   std::conditional_t<I & 4 == 0, types<int>, types<>> +
                                   std::conditional_t<I & 2 == 0, types<double>, types<>>{} +
                                   std::conditional_t<I & 1 == 0, types<float>,  types<>>{})::template apply<std::tuple>
                           >

and there you go.

Of course in c++20 you can pass the types object directly.

Manicotti answered 19/4, 2021 at 4:55 Comment(0)
A
0

Let me give an alternative solution (see Sam's answer for details):

template<bool... Fs, class... Ts>
auto filter_tuple(std::tuple<Ts...>) {
    return std::tuple_cat(
        std::conditional_t<Fs, std::tuple<Ts>, std::tuple<>>()...);
}

template<typename T>
class Base { };

template<int I>
using Base_t = decltype(filter_tuple<!(I % 4), !(I % 2),  !(I % 1)>(
                          std::tuple<int,      double,    float>{}));

static_assert(std::is_same_v<Base_t<1>, std::tuple<float>>);
static_assert(std::is_same_v<Base_t<2>, std::tuple<double, float>>);
static_assert(std::is_same_v<Base_t<4>, std::tuple<int, double, float>>);

Demo

Andriette answered 19/4, 2021 at 7:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.