unclear variadic template recursion

Hi,

which of the 2 codes is correct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class Opt>
constexpr bool has() const {
                return tuple_helper::tuple_contains_type<Opt, constraints_type>::value;
}

template<class O1, class O2, class... Opts>
constexpr bool has_every() const {
      if (has<O1>() && has<O2>()) {
             return true;
      }
      else {
             return has_every<Opts...>();
     }
}

template<class O1>
constexpr bool has_every() const {
      return has<O1>();
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class Opt>
constexpr bool has() const {
                return tuple_helper::tuple_contains_type<Opt, constraints_type>::value;
}

template<class O1, class O2, class... Opts>
constexpr bool has_every() const {
      if (has<O1>() && has<O2>() && has_every<Opts...>()) {
             return true;
      }
      else {
             return has_every<Opts...>();
     }
}

template<class O1>
constexpr bool has_every() const {
      return has<O1>();
}


constraints_type is a tuple.
The intent is:

 
for all Oi, has<Oi> must be true for has_every<Tuple> to return true


just pure logic

Regards,
Juan Dent
Both have problems with an even number of types. Consider this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<class Opt>
 constexpr bool has() {
                return tuple_helper::tuple_contains_type<Opt, constraints_type>::value;
}

    struct Xx{};

template<class O1 = Xx>
constexpr  bool has_every()  {
      return has<O1>(); // std::is_same<O1, Xx>::value
}


template<class O1, class O2, class... Opts>
constexpr  bool has_every()  {
             return has<O1>() && has<O2>() && has_every<Opts...>();
}

int main()
{
    struct X1{};
    struct X2{};
    struct X3{};
    struct X4{};

  has_every<X1, X2, X3, X4>();

    return 0;
}
Or since 17:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<class Opt>
 constexpr bool has() {
                return tuple_helper::tuple_contains_type<Opt, constraints_type>::value;
}


template<class O1, class... Opts>
constexpr  bool has_every()  {
  if constexpr (sizeof...(Opts) > 0)
    return has<O1>() && has_every<Opts...>();
  else
    return has<O1>();
}

int main()
{
    struct X1{};
    struct X2{};
    struct X3{};
    struct X4{};

  has_every<X1, X2, X3, X4>();

    return 0;
}
Last edited on
Your solutions @coder777 also have problems with even number of types? Or are these your proposals for correct implementation?

Seems to me your C++17 solution is correct! not sure about the other one...
Last edited on
Your solutions @coder777 also have problems with even number of types?
No, you can easily check that.

The first solution has the problem what to do with Xx (std::is_same<O1, Xx>::value as an hint).

When you havve 17 (you should) prever the second solution.
As a matter of fact your 17 solution IS correct; not the first code..
C++17
1
2
template <typename... Elts>
  constexpr bool has_every() const noexcept { return (has<Elts>() && ...); }


An implementation (as namespace scope functions) might look somewhat like this:
1
2
3
4
5
6
7
8
9
10
template <typename...> struct tuple_contains; 
// could also inherit `std::conjunction</*...*/>`
template <typename... Elts, typename T> 
  struct tuple_contains<std::tuple<Elts...>, T>
    : std::bool_constant<(std::is_same_v<T, Elts> || ...)> {}; 
template <typename Tuple, typename T>
  bool constexpr tuple_contains_v = tuple_contains<Tuple, T>();
  
template <typename Tuple, typename... Ts>
  bool constexpr tuple_contains_all_v = (tuple_contains_v<Tuple, Ts> && ...); 
Last edited on
mbozzi wrote:
template <typename... Elts>
constexpr bool has_every() const noexcept { return (has<Elts>() && ...); }
What's the magic behind (has<Elts>() && ...)? Is the && ... automatically removed when ... contains nothing? Is ... in this case a recursive call?

JUANDENT wrote:
not the first code..
That requires a bit more work...
It's a pack expansion called a fold expression. Specifically a unary right fold.

In general the compiler expands (has<Elts>() && ...) into
(has<Elts1>() && (has<Elts2>() && (... && (has<EltsN-1>() && has<EltsN>()))))

For example
- if Elts is the parameter pack containing A, B, C, D, then (has<Elts>() && ...) expands into
(has<A>() && (has<B>() && (has<C>() && has<D>())))
- If Elts is the parameter pack containing just one parameter A, then (has<Elts>() && ...) expands into (has<A>())
- If Elts is the empty parameter pack (has<Elts>() && ...) expands into true, a special case.

https://en.cppreference.com/w/cpp/language/fold
https://www.foonathan.net/2020/05/fold-tricks/
Last edited on
Okay, thank you for the explanation.
Registered users can post here. Sign in or register to post.