Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ concepts for template-values and parameter pack

Was wondering what syntax would be the most effective/convenient for concepts on parameters pack, including values template-parameters when types (ou values' types) depend on each other?

For instance, with the following function :

equal_v<values...> is true when all values are equal

template <auto... values>
constexpr static auto equal_v = []() consteval
{
    static_assert(sizeof...(values) > 0, "equal_v : no arguments");
    constexpr auto first_value = std::get<0>(std::tuple{values...});
    static_assert(
        (std::equality_comparable_with<decltype(values), decltype(first_value)> && ...),
        "equal_v : cannot compare values");
    return ((values == first_value) && ...);
}
();

I would have :

template <typename ... Ts>
concept are_equality_comparable = requires(Ts ... values)
{
    { 
        std::conditional_t<(std::equality_comparable_with<decltype(std::get<0>(std::tuple{values...})), decltype(values)> && ...), std::true_type, std::false_type>{}
    } -> std::same_as<std::true_type>;
};

template <auto ... values>
    requires(are_equality_comparable<decltype(values)...>)
constexpr static auto equal_v = []() consteval {
    static_assert(sizeof...(values) > 0, "equal_v : no arguments");
    constexpr auto first_value = std::get<0>(std::tuple{values...});
   
    return ((values == first_value) && ...); 
}();

Which is kinda ugly, using std::conditional<..., std::true_type, std::false_type> as std::same_as<..., std::true_type> arguments.

So I ends up with the following syntax, which I think is the most elegant :

template <auto first_value, auto ... values>
    requires (std::equality_comparable_with<decltype(first_value), decltype(values)> && ...)
constexpr static auto equal_v = []() consteval {
    return ((values == first_value) && ...); 
}();

or, with not mandatory first parameter :

template <auto ... values>
    requires (std::equality_comparable_with<decltype(std::get<0>(std::tuple{values...})), decltype(values)> && ...)
constexpr static auto equal_v = []() consteval {
    return ((values == std::get<0>(std::tuple{values...})) && ...); 
}();

However, as all values depends on the first one,
I did not figure out a way using such hypothetic syntax yet :

template <auto first_value, std::equality_comparable_with<decltype(first_value)> ... values>
constexpr static auto equal_v = []() consteval {
    return ((values == first_value) && ...); 
}();

So, here are some points :

  • Is there an elegant way to use concepts to restrict template-values's types instead of template-type-arguments?
  • Is there a way to restrict types in a template-parameter-pack that depends on each other without modifying the function signature, without the requires clause? e.g template <auto ... values> instead of template <auto first_value, auto ... values>

[EDIT] As pinpointed by artyer in the comments, concept auto value is a valid syntax.
Also, this does not work with MSVC 19.28 as it results in :

error C7601: the associated constraints are not satisfied

nor GCC 10.2 :

error: placeholder constraints not satisfied

But works fine with Clang 11.0.1.

template <auto first_value, std::equality_comparable_with<decltype(first_value)> auto ... values> constexpr static auto equal_v = []() consteval {
   return ((values == first_value) && ...);
}();

[EDIT_2]

In order to make equal_v<> true :

template <auto first_value = int{}, std::equality_comparable_with<decltype(first_value)> auto ... values>
constexpr static auto equal_v = []() consteval {
   return ((values == first_value) && ...);
}();
like image 595
Guss Avatar asked Oct 14 '25 08:10

Guss


1 Answers

Well, as conclusion, here are answers to the two questions :

  • How to apply constraints on template-value-parameters ?

    As pin-pointed by @Artyer and @Davis Herring, concept auto value is a legal syntax defined in p1141r2.
    NB : It's not currently part of cppreference documentation.

  • How to apply constraints on parameters-packs, when types depend on each-others ?

    • Using constrained-auto (concept (auto) arg syntax) :

      Defining the first one with a default value (to preserve parameterless resolutions),
      and applying the contraints on a parameter-pack that contain parameters which remain.

      template <auto first_value = int{}, 
                    std::equality_comparable_with<decltype(first_value)> auto ... values>
          constexpr static auto equal_v = []() consteval {
             return ((values == first_value) && ...);
          }();
      
    • Using requires-clause syntax :

      template <auto ... values>
        requires (std::equality_comparable_with<
           decltype(std::get<0>(std::tuple{values...})),
           decltype(values)
        > && ...)
      constexpr static auto equal_v = []() consteval {
      return ((values == std::get<0>(std::tuple{values...})) && ...); 
      }();
      
like image 112
Guss Avatar answered Oct 16 '25 23:10

Guss



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!