通过参数组合C++中的概念

vzgqcmou  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(194)

似乎很难将C概念组合成一种可以用作对一些新模板的约束的方式,因为概念模板没有完全被视为类模板(只是一种)。
实际上,我想做的工作在Visual Studio编译器上,但不是clang/g

Compiler Explorer Illustration
背景

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = ranges::range<ITERABLE> and is_convertible_v<ranges::range_value_t<ITERABLE>, OF_T>;
  3. static_assert (IIterable<vector<int>, int>);
  4. static_assert (IIterable<vector<long int>, int>);
  5. static_assert (IIterable<vector<int>, long int>);
  6. static_assert (not IIterable<vector<string>, int>); // legit 'iterable' but cannot convert string to int

然后用作对一些其他模板的约束(例如,容器的构造函数参数)。

  1. template <typename T>
  2. struct Collection { ...
  3. template <IIterable<T> ITERABLE_OF_ADDABLE>
  4. Collection (ITERABLE_OF_ADDABLE&& src)

这在我测试过的所有编译器上都运行得很好。但是现在我希望泛化Iterable的概念,所以我对输入迭代对象的元素有一个通用的约束

  1. // 'range + some other constraint'
  2. template <typename ITERABLE, template <typename> typename ITEM_PREDICATE>
  3. concept IIterableWith = ranges::range<ITERABLE> and ITEM_PREDICATE<ranges::range_value_t<ITERABLE>>::value;

到目前为止一切顺利。现在它得到了一个小把戏。如何定义元素上的“constaint”。我想使用原生C++概念'currying'概念,并且能够传入convertible_to

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable1 = IIterableWith<ITERABLE, convertible_to<OF_T>>;

但是模板类型currying似乎不适用于这种上下文中的任何编译器。没问题我可以自己写。

  1. template <typename T>
  2. struct IsAddableOfT {
  3. // extra confusing level of struct/tests due to fact that type currying only works on concepts
  4. // not class templates, and cannot just write two 'template' statements in a row for an alias template - must break up like this.
  5. template <typename POTENTIALLY_ADDABLE_T>
  6. using Test = is_convertible<POTENTIALLY_ADDABLE_T, T>;
  7. };

这在我选择的任何编译器上都工作得很好(到目前为止)。
现在的问题!

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, typename IsAddableOfT<OF_T>::Test>;

这在visual studio编译器上工作正常(参见上面的编译器资源管理器链接)-但在gcc和clang上失败(注意,使用visual studio需要在IsAddableOfT之前输入typename,并尝试使用模板和不使用clang/gcc:仍然没有运气)。

  1. type/value mismatch at argument 2 in template parameter list for 'template<class ITERABLE, template<class> class ITEM_PREDICATE> concept IIterableWith

注意-一个很好的解决方案是如果存在一个CONCEPT(不是类型trait),比如:

  1. template <concept <typename>A, concept <typename>B>
  2. concept conjunction = A && B
txu3uszq

txu3uszq1#

你必须使用template消歧器,而不是typename,因为IsAddableOfT::Test不是类型,而是模板。以下代码与GCC 13 -std=c++20编译:

  1. #include <concepts>
  2. #include <type_traits>
  3. #include <ranges>
  4. #include <array>
  5. template <typename ITERABLE, template <typename> typename ITEM_PREDICATE>
  6. concept IIterableWith = std::ranges::range<ITERABLE>
  7. and ITEM_PREDICATE<std::ranges::range_value_t<ITERABLE>>::value;
  8. // note: IsConvertibleTo seems like a more appropriate name, given how
  9. // it is implemented.
  10. // (it doesn't have anything to do with the plus operator)
  11. template <typename TO>
  12. struct IsConvertibleTo {
  13. template <typename FROM>
  14. using Test = std::is_convertible<FROM, TO>;
  15. };
  16. // 'template' disambiguator necessary here because Test is dependent on OF_T here.
  17. // The compiler cannot know what the meaning of Test is without it, and it's
  18. // not a type.
  19. template <typename ITERABLE, typename OF_T>
  20. concept IIterable = IIterableWith<ITERABLE, IsConvertibleTo<OF_T>::template Test>;
  21. // Template disambiguator unnecessary here, because the compiler knows the
  22. // definition of IsConvertibleTo<int> and thus knows what ::Test means.
  23. static_assert(IIterableWith<std::array<int, 10>, IsConvertibleTo<int>::Test>);
展开查看全部
ejk8hzay

ejk8hzay2#

visual studio是错误的,而且总是错误的代码类型。
看看IIterableWith

  1. template <typename ITERABLE, template <typename> typename ITEM_PREDICATE>
  2. concept IIterableWith = ...

正如您所看到的,第二个参数是 * template *,而不是类型名。
现在让我们玩个游戏。你认为这应该编译吗?

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, int>;

现在我打赌你会回答我没有,因为int本身不是模板。
好的,还有一步:这个怎么样?

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, OF_T>;

如果你仍然回答我,它必须不编译,你是对的! OF_T不是模板。但是编译器怎么知道呢?编译器甚至不知道OF_T实际上是什么,它仍然是一个需要填充的模板参数。
这是因为编译器仍然必须在模板名称查找的第一阶段构造一个部分AST。每个模板参数都有 * kind *。OF_Ttypename,所以我们知道它不能是模板或值。它必须是一种类型。这是好事!这意味着编译器可以在示例化模板之前验证代码是否有意义。
现在让我们更进一步,再添加一层。这个编译吗?

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, std::vector<OF_T>::value_type>;

当然不是!你必须告诉value_type是正确的吗?value_type必须是一个类型,对吗?这是因为编译器假定依赖名称是值而不是类型。假设::value_type是这个表达式的一个类型。让我们修复它:

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, typename std::vector<OF_T>::value_type>;

好了但它仍然不会编译。
但是等一下!为什么我们必须这样做,为什么value_type被假设为值而不是类型?
这是因为编译器不知道std::vector<OF_T>的内容是什么。根据OF_T是什么,std::vector可以接受任何专业化。您可能会想象有人用static int value_type;定义示例化。编译器不能假设任何内容,因为它是一个 * 依赖类型 *。依赖于模板的类型。依赖类型和模板参数一样未知!
所以在依赖表达式上,就像这样:typename std::vector<OF_T>::value_typevalue_type是实际类型的唯一原因是因为typename
现在一切都好了,让我们试试你的代码:

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, typename IsAddableOfT<OF_T>::Test>;

所以这里你说::Test必须是一个类型,就像int一样。
在编译器看来,Test必须和intOF_T一样是一个类型,因为你明确地说你期望::Test是一个类型。
你必须告诉编译器Test是一个模板,而不是说它是一个类型:

  1. template <typename ITERABLE, typename OF_T>
  2. concept IIterable = IIterableWith<ITERABLE, IsAddableOfT<OF_T>::template Test>;

现在起作用了! IIterableWith需要一个模板,在这个表达式中,编译器预先知道Test也必须是一个模板。匹配就能编译。

展开查看全部

相关问题