Почему конкретное ограничение концепции дублируется в созданном шаблоне?

Я только начал экспериментировать с концепциями C ++, используя g ++ 6.1.0 (версия пакета Debian 6.1.1-1, флаги -std=c++17 -fconcepts), и я столкнулся с сообщением об ошибке, которое я не понимаю в следующем сокращенном примере:

#include <iterator>
#include <vector>
#include <iostream>

template <typename B>
concept bool ContextualBool = requires(B b) {
  { bool(b) };
  { !b } -> bool;
};

template <typename It>
concept bool InputIterator = requires(const It iconst, const It jconst, It i) {
  typename std::iterator_traits<It>::reference;
  typename std::iterator_traits<It>::value_type;
  { iconst == jconst } -> ContextualBool;
  { iconst != jconst } -> ContextualBool;
  { *i } -> typename std::iterator_traits<It>::reference;
  { ++i } -> It&;
  { *i++ } -> typename std::iterator_traits<It>::value_type;
};

template <typename P, typename Arg>
concept bool Predicate = requires(P pred, Arg x) {
  { pred(x) } -> ContextualBool;
};

template <typename P, typename It>
concept bool InputPredicate = requires {
  typename std::iterator_traits<It>::reference;
  requires Predicate<P, typename std::iterator_traits<It>::reference>;
};

/* Version 1 */
/*
InputIterator{I}
bool all_of(I begin, I end, InputPredicate<I> pred) {
*/
/* Version 2 */
/*
bool all_of(InputIterator begin, InputIterator end,
Predicate<typename std::iterator_traits<InputIterator>::reference> pred) {
*/
/* Version 3 */
bool all_of(InputIterator begin, InputIterator end,
            InputPredicate<InputIterator> pred) {
  while (begin != end) {
    if (!pred(*begin))
      return false;
    ++begin;
  }
  return true;
}

int main() {
  std::vector<int> v { 1, 2, 3, 4, 5 };
  if (all_of(v.begin(), v.end(), [](int n) { return n % 2 == 0; }))
    std::cout << "All elements of v are even\n";
  return 0;
}

В этом коде и версия 1, и версия 2 успешно компилируются с ожидаемыми результатами выполнения. Однако с версией 3 я получаю следующие сообщения об ошибках:

/tmp/concepts_repr.cpp: In function ‘int main()’:
/tmp/concepts_repr.cpp:56:70: error: no matching function for call to ‘all_of(std::vector<int>::iterator, std::vector<int>::iterator, main()::<lambda(int)>)’
       if (all_of(v.begin(), v.end(), [](int n) { return n % 2 == 0; }))
                                                                      ^
/tmp/concepts_repr.cpp:44:10: note: candidate: template<class auto:1, class auto:2, class auto:3, class auto:4>  requires predicate( InputIterator<auto:1>) and predicate(InputPredicate<auto:2, auto:1>) and predicate(InputIterator<auto:3>) and predicate(InputPredicate<auto:4, auto:3>) bool all_of(auto:1, auto:1, auto:4)
     bool all_of(InputIterator begin, InputIterator end,
          ^~~~~~
/tmp/concepts_repr.cpp:44:10: note:   template argument deduction/substitution failed:
/tmp/concepts_repr.cpp:56:70: note:   couldn't deduce template parameter ‘auto:2’
       if (all_of(v.begin(), v.end(), [](int n) { return n % 2 == 0; }))
                                                                      ^

Судя по сообщению об ошибке, похоже, что по какой-то причине в сгенерированном шаблоне создаются повторяющиеся версии параметров шаблона InputIterator и InputPredicate. Я не понимаю, почему это могло быть, особенно учитывая, что версия 2 работала. Не понимаю ли я утверждение cppreference.com: «Все заполнители, введенные эквивалентными спецификаторами ограниченного типа, имеют один и тот же изобретенный параметр шаблона»? Или это, возможно, ошибка в gcc?


person Daniel Schepler    schedule 21.05.2016    source источник


Ответы (1)


Из того, что я могу собрать, концепции работают с именами типов, а не с концепцией имен концептов. В версии 3 кода у нас есть InputPredicate<InputIterator>, но InputIterator - это имя концепции, а не имя типа. Отсюда и ошибки компиляции.

На самом деле мы хотим, чтобы концепция InputIterator работала с типами begin и end. Этого можно добиться с помощью небольшого изменения сигнатуры функции:

bool all_of(InputIterator begin, InputIterator end,
        InputPredicate<decltype(begin)> pred) {
  ...
}

В качестве альтернативы мы можем явно указать параметры шаблона и использовать предложение requires, чтобы наложить на них ограничения:

template<typename It, typename P>
  requires InputIterator<It> && InputPredicate<P, It>
bool all_of(It begin, It end, P pred) {
  ...
}

На самом деле я предпочитаю последний синтаксис, поскольку я думаю, что он делает предполагаемое ограничение на выводимые параметры шаблона более ясным.

person Chris Hayden    schedule 21.05.2016
comment
InputIterator используется как заполнитель, когда он появляется в InputPredicate<InputIterator>, почти так же, как когда он используется в объявлениях параметров begin и end. Это не ошибка сама по себе, даже в момент создания экземпляра. Дополнительные примеры заполнителей. - person Luc Danton; 22.05.2016
comment
Интересный. Есть ли документация, в которой концепции описываются как заполнители? Я просмотрел предложения по стандартам, но не увидел ничего, явно связанного с этим вариантом использования. - person Chris Hayden; 23.05.2016