对类元组类型递归 concept/type_traits
Recursive concept/type_traits on tuple-like types
说我正在尝试实施一个概念 meowable
- 整数类型是可喵的。
- Class 成员函数为
meow
的类型是可喵的。这是最终目标,但当前问题没有关注它。
- 只有可喵元素的类似元组的类型是可喵的。
- std::ranges::range with meowable 元素是可喵喵的。这是最终目标,但当前问题没有关注它。
然后我想出了这个实现(尽可能简化):
#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.
但是,如果我取消注释倒数第二行代码,所有编译器都会很高兴。 (概念的实例化点?)
所以我的问题是:
- 为什么会出现这种行为? (编译器错误或类似“格式错误的 NDR”之类的东西?)
- 我怎样才能实现我的目标?
- Why this behavior? (compiler bug or something like "ill-formed NDR"?)
这显然是 GCC-trunk 和 Clang-trunk 的错误,这里的问题是 GCC/Clang 没有根据 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);
- How can I achieve my target?
根据归约结果用模板函数替换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>>{});
我写了一个 GENERIC 版本(使用 SFINAE),带有 可自定义的谓词 ,它将适用于所有专门的 tuple-like 类型(具有 std::tuple_element
和 std::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
谓词的用法。
旧答案
这是一个简单的递归 SFINAE 解决方案,适用于 C++17(和 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;
};
说我正在尝试实施一个概念 meowable
- 整数类型是可喵的。
- Class 成员函数为
meow
的类型是可喵的。这是最终目标,但当前问题没有关注它。 - 只有可喵元素的类似元组的类型是可喵的。
- std::ranges::range with meowable 元素是可喵喵的。这是最终目标,但当前问题没有关注它。
然后我想出了这个实现(尽可能简化):
#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.
但是,如果我取消注释倒数第二行代码,所有编译器都会很高兴。 (概念的实例化点?)
所以我的问题是:
- 为什么会出现这种行为? (编译器错误或类似“格式错误的 NDR”之类的东西?)
- 我怎样才能实现我的目标?
- Why this behavior? (compiler bug or something like "ill-formed NDR"?)
这显然是 GCC-trunk 和 Clang-trunk 的错误,这里的问题是 GCC/Clang 没有根据 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);
- How can I achieve my target?
根据归约结果用模板函数替换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>>{});
我写了一个 GENERIC 版本(使用 SFINAE),带有 可自定义的谓词 ,它将适用于所有专门的 tuple-like 类型(具有 std::tuple_element
和 std::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
谓词的用法。
旧答案
这是一个简单的递归 SFINAE 解决方案,适用于 C++17(和 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;
};