Search code examples
c++templatesmemberc++20concept

How to write a c++ concept for class with member function template that takes a template argument?


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).


Solution

  • 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.