Home > Mobile >  Recursive concept/type_traits on tuple-like types
Recursive concept/type_traits on tuple-like types

Time:05-19

Say I was trying to implement a concept meowable that

  1. Integral types are meowable.
  2. Class types with member function meow are meowable. This is in the final target but the current question doesn't focus on it.
  3. Tuple-like types with only meowable elements are meowable.
  4. std::ranges::range with meowable elements are meowable. This is in the final target but the current question doesn't focus on it.

Then I came up with this implementation(simplified as I could):

#include <concepts>
#include <type_traits>
#include <ranges>
#include <utility>
#include <tuple>

template<class T>
concept meowable_builtin = std::integral<T>;

template<class T, std::size_t I>
concept has_tuple_element = requires (T t) {
  typename std::tuple_element<I, T>::type;
  { get<I>(t) } -> std::convertible_to<std::tuple_element_t<I, T>&>;
};

template<class T>
concept tuple_like = requires {
    typename std::tuple_size<T>::type;
    { std::tuple_size_v<T> } -> std::convertible_to<std::size_t>;
  } &&
  []<std::size_t...I>(std::index_sequence<I...>) {
    return (has_tuple_element<T, I> && ...);
  } (std::make_index_sequence<std::tuple_size_v<T>>{});

template<class               T> struct is_meowable:    std::false_type{};
template<meowable_builtin    T> struct is_meowable<T>: std::true_type{};

template<tuple_like T>
struct is_meowable<T>
  : std::bool_constant<
      []<std::size_t...I>(std::index_sequence<I...>) {
        return (is_meowable<std::tuple_element_t<I, T>>::value && ...);
      } (std::make_index_sequence<std::tuple_size_v<T>>{})
    > {};

template<class T>
concept meowable_tuple = tuple_like<T> && is_meowable<T>::value;

template<class T>
concept meowable = is_meowable<T>::value;

static_assert(meowable<int>);
//static_assert(tuple_like<std::tuple<int>>);
static_assert(is_meowable<std::tuple<int>>::value);

But some compilers don't like it (https://godbolt.org/z/5vMTEhTdq):

1. GCC-12 and above:   internal compiler error.
2. GCC-11:             accepted.
3. Clang-13 and above: static_assert fired.
4. MSVC-v19:           accepted.

However, if I uncomment the second last line of code, all compilers are happy. (Instantiation point of concepts?)

So my questions are:

  1. Why this behavior? (compiler bug or something like "ill-formed NDR"?)
  2. How can I achieve my target?

CodePudding user response:

  1. Why this behavior? (compiler bug or something like "ill-formed NDR"?)

This is apparently a bug of GCC-trunk and Clang-trunk, the issue here is that GCC/Clang doesn't properly handle the template partial specialization based on the concept initialized by the lambda. Reduced

template<class>
concept C = [] { return true; } ();

template<class T> 
struct S {};

template<class T>
  requires C<T>
struct S<T> { constexpr static bool value = true; };

// static_assert(C<int>);
static_assert(S<int>::value);
  1. How can I achieve my target?

Replace lambda with the template function based on the reduced result

template<class T, std::size_t...I>
constexpr bool all_has_tuple_element(std::index_sequence<I...>) {
  return (has_tuple_element<T, I> && ...);
}

template<class T>
concept tuple_like = requires {
  typename std::tuple_size<T>::type;
  { std::tuple_size_v<T> } -> std::convertible_to<std::size_t>;
} && all_has_tuple_element<T>(std::make_index_sequence<std::tuple_size_v<T>>{});

Demo

CodePudding user response:

Here is a simple recursive SFINAE solution for C 17 (and 11 for some adjustments):
https://godbolt.org/z/7sMjfq7ze


#include <type_traits>

template <typename T, typename = void>
struct has_meow : std::false_type {};

// this is a customization point to deduce tuple-like traits
// you can define some default logic here,
// but for the sake of example let it be false by default
template <typename>
struct is_tuple_like : std::false_type {};

template <typename T>
struct has_meow<T, std::void_t<decltype(std::declval<T>().mew())>> : std::true_type {};

template <typename T>
struct meowable : std::disjunction<std::is_integral<T>, has_meow<T>> {};

template <template <typename...> class TupleT, typename ... T>
struct meowable<TupleT<T...>> : std::conjunction<
                                          is_tuple_like<TupleT<T...>>,
                                          std::disjunction<meowable<T>...
                                       > {};

#include <tuple>
#include <array>

// this will also catch std::pair
template <typename ... T>
struct is_tuple_like<std::tuple<T...>> : std::true_type {};

template <typename T, size_t N>
struct is_tuple_like<std::array<T, N>> : std::true_type {};

struct cat
{
    void mew() {}
};

int main()
{
    static_assert(meowable<int>::value);
    static_assert(meowable<std::tuple<int, long>>::value);
    static_assert(meowable<cat>::value);
    static_assert(meowable<std::tuple<cat, long>>::value);
    static_assert(meowable<std::tuple<int, std::tuple<cat, long>>>::value);

    return 0;
};
  • Related