假设我试图实现一个概念 meowable
- 整数类型是可喵的。
- 具有成员函数
meow
的类类型是可喵喵叫的。这是最终的目标,但当前的问题并不关注它。 - 只有可喵元素的类元组类型是可喵的。
- 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.
但是,如果我取消注释倒数第二行代码,所有编译器都会很高兴。 (概念的实例化点?)
所以我的问题是:
- 为什么会有这种行为? (编译器错误或类似“格式错误的 NDR”之类的东西?)
- 我怎样才能实现我的目标?
回答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);
- 我怎样才能实现我的目标?
将 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>>{});
回答2
我编写了一个带有可自定义谓词的 GENERIC 版本(使用 SFINAE),该谓词将适用于所有专门的类元组类型(那些具有 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
谓词的用法。
旧答案
这是 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;
};