c++ - 类元组类型上的递归概念/type_traits

假设我试图实现一个概念 meowable

  1. 整数类型是可喵的。
  2. 具有成员函数 meow 的类类型是可喵喵叫的。这是最终的目标,但当前的问题并不关注它。
  3. 只有可喵元素的类元组类型是可喵的。
  4. std::ranges::range 带有可喵元素是可喵的。这是最终的目标,但当前的问题并不关注它。

然后我想出了这个实现(尽可能简化):

#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);

但有些编译器不喜欢它(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.

但是,如果我取消注释倒数第二行代码,所有编译器都会很高兴。 (概念的实例化点?)

所以我的问题是:

  1. 为什么会有这种行为? (编译器错误或类似“格式错误的 NDR”之类的东西?)
  2. 我怎样才能实现我的目标?

回答1

  1. 为什么会有这种行为? (编译器错误或类似“格式错误的 NDR”?)

这显然是 GCC-trunk 和 Clang-trunk 的错误,这里的问题是 GCC/Clang 没有正确处理基于 lambda 初始化的概念的模板部分特化。 https://godbolt.org/z/8hbnb6ofe

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. 我怎样才能实现我的目标?

将 lambda 替换为基于缩减结果的模板函数

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>>{});

https://godbolt.org/z/dTdz8re14

回答2

我编写了一个带有可自定义谓词的 GENERIC 版本(使用 SFINAE),该谓词将适用于所有专门的类元组类型(那些具有 std::tuple_elementstd::tuple_size 特化的类型)。

https://godbolt.org/z/Yf889GY6E

#include <cstddef>
#include <type_traits>
#include <tuple>

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

template <typename T>
struct is_complete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {};

// catching all standard (and specialized) tuple-like types
template <typename T>
struct is_tuple_like : is_complete<std::tuple_size<T>> {};

template <typename T, bool = is_tuple_like<T>::value>
struct get_tuple_size
{
    constexpr static size_t value = 0;
};

template <typename T>
struct get_tuple_size<T, true>
{
    constexpr static size_t value = std::tuple_size_v<T>;
};

// generic solution with predicate
template <template <typename> class PredicateT,
    typename T, 
    bool = is_tuple_like<T>::value, 
    typename = decltype(std::make_index_sequence<get_tuple_size<T>::value>{})>
struct tuple_conjunction : PredicateT<T> {};

template <template <typename> class PredicateT,
    typename T, 
    size_t ... I>
struct tuple_conjunction<PredicateT, T, true, std::index_sequence<I...>> : 
    std::conjunction<tuple_conjunction<PredicateT, std::tuple_element_t<I, T>>...> {}; 


////////////////////////////

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

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

template <typename T>
using meowable_pred = std::disjunction<std::is_integral<T>, has_meow<T>>;

template <typename T>
using meowable = tuple_conjunction<meowable_pred, T>;


#include <array>

struct cat
{
    void mew() {}
};

struct dummy{};

int main()
{
    static_assert(!is_complete<std::tuple_element<0, cat>>::value);
    static_assert(is_complete<std::tuple_element<0, std::tuple<cat>>>::value);

    static_assert(meowable<int>::value);
    static_assert(meowable<cat>::value);
    static_assert(!meowable<dummy>::value);

    static_assert(meowable<std::tuple<long>>::value);

    static_assert(meowable<std::tuple<int, long>>::value);
    static_assert(meowable<std::tuple<cat, long>>::value);
    static_assert(meowable<std::tuple<int, std::tuple<cat, long>>>::value);
    
    static_assert(!meowable<std::tuple<int, std::tuple<cat, long>, dummy>>::value);

    // std::array 
    static_assert(meowable<std::array<cat, 42>>::value);
    static_assert(meowable<std::tuple<int, std::tuple<cat, long, std::tuple<std::array<cat, 42>>>>>::value);

    return 0;
};

通过一些调整,它也适用于 C++11。

  • 您可以提供任何您想要的谓词进行递归检查。该示例显示了 meowable_pred 谓词的用法。

旧答案

这是 C++17 的简单递归 SFINAE 解决方案(以及 11 进行一些调整):

https://godbolt.org/z/cezqhb99b

#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::conjunction<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;
};

相似文章

最新文章