2

How can I write a C++ 20 concept requiring a member function template that takes a template argument that must be provided?

The goal of the following concept is to check that a type is tuple-like, except rather than checking std::get<I>(t) I want to check t.get<I>().

The following does not compile with g++ 10.1 using switch -std=c++2a.

#include <concepts>

template <typename E> 
concept Tpl = requires(E const e, int idx)
   {
      {
         e.template get<idx>()
      } -> std::convertible_to<float>;
   };

if template is not used before get, it is of course does not compile (no angle braces, less/greater than operator).

Barry
  • 286,269
  • 29
  • 621
  • 977
koraxkorakos
  • 369
  • 1
  • 10
  • I'm not sure what you're asking here - what do you mean by "explicitly stated argument type (note deduceable)"? Which argument are we talking about here? – Barry Feb 20 '21 at 18:02
  • 1
    I assume he wants some `Tuple-Like` class which has a member function wich behaves similar to `std::get(TupleLike)` - but just as member function. – Bernd Feb 20 '21 at 18:07
  • @Bernd If that's what OP wants, then OP can edit the question to say that. Or if they want something different, they should say something different. – Barry Feb 20 '21 at 18:13
  • yes like in std::tuple. Since there are no arguments to the member function the member function template parameter cannot be deduces. This is by design and the template parameter is stated with the call: `e.get()` in a in templated call, `e.template get()`. – koraxkorakos Feb 20 '21 at 18:18
  • 1
    Does it *have* to be a parametrized integer? Wouldn't checking for the validity of `e.template get<(int)0>()` be enough? – StoryTeller - Unslander Monica Feb 20 '21 at 18:29
  • basically it is required that `get()` exists for all integers, i.e. none removed with `enable_if_t`, but I think this is not possible to be checked. – koraxkorakos Feb 20 '21 at 18:58
  • @koraxkorakos - How can that be possible? All tuples are limited in number of elements. A limit that is almost guaranteed to be much lower than INT_MAX. – StoryTeller - Unslander Monica Feb 20 '21 at 20:00
  • It has to be. In my application (multivector) I use `std::uintmax_t`. It is like a sparse tuple, where most elements are known to be 0 already known at compile time. This gives me 64 (assuming this is the number of bits of the uint used) generators to combine, e. g. a 2d versor would have indices 0 and 3 (bit 0 and 1) stored, all the rest are 0. – koraxkorakos Feb 21 '21 at 14:19

2 Answers2

5

This:

template <typename E> 
concept Tpl = requires(E const e, int idx)
   {
      {
         e.template get<idx>()
      } -> std::convertible_to<float>;
   };

does not work because idx is not a constant expression, and you need it to be one in the context of calling get.

In order to do that, you need to pick a specific value of the index in order to check. The only one you can really pick is 0:

template <typename E> 
concept Tpl = requires (E const e) {
    { e.template get<0>() } -> std::convertible_to<float>;
};

This will potentially match a function template get which has a template parameter of type int* or nullptr_t. If that's a concern, we can spell 0 in a more complicated way to avoid it being a null pointer constant, whether that's simply 1-1 or taking as a second "parameter" an object of type integral_constant<size_t, 0>:

template <typename E> 
concept Tpl = requires (E const e, std::integral_constant<size_t, 0> value) {
    { e.template get<value()>() } -> std::convertible_to<float>;
};

If ultimately, the goal is to check that e.get<I>() works for all I from [0, tuple_size_v<E>), then you'll have to approach this a bit differently. In that context, you'll want the index you're checking to be part of the concept itself. As in:

template <typename E, size_t I>
concept tuple_like_impl = requires (E const e) {
    { e.template get<I>() } -> std::convertible_to<float>;
};

And then build up a parameter pack of Is... such that you eventually construct a constraint like (tuple_like_impl<E, Is> && ...).

But that's a step you should take only if you actually need to.

Barry
  • 286,269
  • 29
  • 621
  • 977
-1

To let it compile you could write it like:

template <typename E, int idx> 
concept Tpl = requires(E const e)
   {
      {
         e.template get<idx>()
      } -> std::convertible_to<float>;
   };

But tell us what you really want.

Bernd
  • 2,113
  • 8
  • 22
  • 1
    While your guess may be reasonable, and even correct some portion of the time, you should try and get clarity on what the OP means *before* answering the question. – cigien Feb 20 '21 at 17:54
  • Thanks, for your response - and I agree. – Bernd Feb 20 '21 at 18:03
  • I do not want to add idx to the class template parameters, but to the function. The background: It is a simplified example of a concept for multivector expressions (expression template), where each expression shall provide coefficient access similar to that in tuple, but sparse. The return value type varies over the indices to allow to distinguish between compile time constants (special class instances that convert to values) or runtime computed vales. computed values. – koraxkorakos Feb 20 '21 at 18:06