Дэвид Санкель в своем Молниеносном выступлении на CppCon 2016 приводит в качестве примера первую концепцию, которую он пытался написать:

Лично я первым написал концепт true. Ваш пробег может отличаться. Эта концепция - тип, который вы можете вызывать с любым {типом, который сам вызывается с int и дает _3 _}, - как указал Дэвид, полностью нереализуема * в Concepts TS. Это связано с тем, что все выражения, которые проверяются на правильность и тип, должны быть экземплярами конкретных типов. Func - конкретный тип, int - конкретный тип. Но IntFunction - это не тип, это концепция.

Что означает указание, что тип должен быть активирован с каким-либо другим типом, который удовлетворяет концепции? И как это проверить? Мы не можем просто попытаться вызвать Func со вселенной типов, удовлетворяющих IntFunction. Таких типов бесконечно много, так что это никогда не сработает. Нам нужно уменьшить эту бесконечность до одного репрезентативного типа IntFunction. Репрезентативный тип концепции обычно называется архетипом. Выбор правильного архетипа определенно нетривиален. Пройдем через процесс. Стандартная библиотека поставляется с типом, который моделирует IntFunction, давайте просто попробуем:

Это начало. std::function<int(int)>, безусловно, IntFunction, но хороший ли это архетип IntFunction? Чтобы ответить на этот вопрос, мы должны более четко обозначить нашу цель. Мы хотим сказать, что тип T моделирует концепцию CallableWithIntFunction тогда и только тогда, когда для каждого типа M, который сам является моделью IntFunction, вы можете вызывать T с M . Соответствующее отрицательное определение было бы, если бы мы могли найти тип M, который моделирует IntFunction, и вы не можете вызвать T с M, тогда T не должен моделировать CallableWithIntFunction.

Учитывая эту цель, успешна ли наша реализация? Рассмотрим этот тип:

evil1::Cимеет модель CallableWithIntFunction1, но на самом деле не соответствует нашим ожиданиям. Есть много-много типов, которые моделируют IntFunction, которые я не могу передать evil1::C. Фактически, почти все из них. Итак, std::function - плохой выбор для архетипа, мы не можем использовать такой хорошо известный класс, как этот. Нам нужен наш собственный тип класса, который не будет конфликтовать.

Давайте попробуем другую реализацию, на этот раз с нашим собственным частным типом:

Так лучше, наша evil1::C представленная модель не модели CallableWithIntFunction2, и это хорошо. Но это все равно не совсем так. Рассмотрим следующий контрпример:

Оба утверждения верны. evil2::F делает модель IntFunction и evil2::C делает модель CallableWithIntFunction2. Но ... мы не можем вызвать C с F. В чем мы ошиблись на этот раз? Опять плохой архетип.

Наш archetypes::IntFunction действительно может быть вызван с int, но, несмотря на то, что у него есть тело, состоящее только из одного объявления, у него есть много других функций из-за неявных операций, генерируемых компилятором. А именно, он по умолчанию является конструктивным, копируемым, перемещаемым, разрушаемым. Ни одну из этих операций мы не проверяли IntFunction! Поэтому, когда мы используем функцию, которая более точно соответствует определению, например evil2::F, которая не подлежит копированию, наш архетип подвел нас. Мы должны быть более конкретными:

С этим более строгим архетипом и evil1::C, и evil2::C терпят неудачу, поскольку оба класса требуют большей функциональности, чем может предоставить только IntFunction. Достаточно ли этого? Все еще нет!

Здесь у нас другая проблема. evil3::F модели IntFunction, но, вероятно, это не то, что мы ожидали. Мы хотели, чтобы функция вызывалась с int, но на самом деле мы указали функцию, вызываемую с lvalue типа int. Эта формулировка допускает такие типы, как evil3::F, которые мы не можем предоставить для C.

Давай попробуем еще:

Вот, пожалуй, самый интересный из случаев. evil4::F делает модель IntFunction: вы можете вызвать ее с помощью int, и она дает результат, который можно преобразовать в int. Но это не дает int, поэтому использование в C не совсем корректно - передача evil4::F в evil4::C приведет к серьезной ошибке из-за ошибки вычета для std::min. Может возникнуть соблазн ограничить IntFunction специально, чтобы получить int, но это слишком ограничительно, чтобы быть практически полезным. Функции, возвращающие что-то вроде bool, должны быть приемлемыми кандидатами на IntFunction, мы не хотим исключать их из рассмотрения. Так в чем же мы ошиблись?

Мы сделали две ошибки. Наш архетип все еще недостаточен, и пользователь нашей концепции плохо себя ведет.

Во-первых, наш архетип должен учитывать тот факт, что оператор вызова может возвращать тип, отличный от int. Мы просто представим еще один частный тип, чтобы удовлетворить это требование:

Во-вторых, наш evil4::C неправильно использует понятие IntFunction. Все, что нам предлагает эта концепция, - это то, что тип вызывается с int и дает тип, который можно преобразовать в int. Это не говорит нам о том, что тип результата int. Использование std::min() в теле делает нашу функцию недостаточно ограниченной. Теперь мы могли бы пойти в двух направлениях: либо (а) использовать более утонченную концепцию, чем IntFunction, которая предусматривает, что тип возвращаемого значения оператора вызова должен быть точно int, либо (б) ограничиться только функциональностью, которую IntFunction предоставляет нам. Последнее дает нам больше возможностей для использования при вводе текста, так что здесь это лучший выбор:

А теперь тип, который может быть вызван с любым типом, который моделирует IntFunction, гарантированно. И мы пришли к концепции, которая точно разрешает и запрещает всех кандидатов надлежащим образом. Наверное. Может быть. Придумать правильный архетип сложно, и в итоге мы получили один из 15 строк для довольно простой концепции. И я даже не совсем уверен, что нет какой-то пары типов evil6::F и evil6::C, которая все равно нарушает это (возможно, нужно удалить арифметические операторы тоже?).

Хотя в Concepts TS можно написать концепцию, которую хотел Дэвид Санкель, это довольно большая работа! Чтобы иметь возможность писать такую ​​концепцию напрямую, компилятор должен иметь возможность конструировать архетипы для всех концепций на лету. Такое построение архетипа не является частью Concepts TS, но мы можем сделать это самостоятельно. Внимательно.

Бонусный слайд. Раз уж вы зашли так далеко, вот вам бонусный слайд. Еще одна вещь, которую вы можете сделать с концепциями. Допустим, я хочу написать fmap вместо optional. В C ++ 17 это довольно просто (для простоты игнорируем всевозможные ссылки):

std::invoke_result_t здесь служит двум целям. Во-первых, для СФИНАЭ. Если функция не вызывается с T, этот шаблон функции будет удален из набора перегрузки. Во-вторых, для фактического типа результата. Этот R нам нужен для реализации этой функции.

Как бы мы могли написать это с помощью концепций?

(Технически нам не нужен Regular, но я хочу избежать ссылок в этом примере, поэтому мне нужно, чтобы F можно было копировать). Ладно ... это выглядит в основном так же. Concepts не дает нам способа получить R (этот тип результата называется связанным типом концепции Invocable), поэтому мы должны использовать тот же C ++ 11/4/7 уловки метапрограммирования, которые мы всегда использовали. В самом деле, использование этого понятия не дает нам здесь многого. Мы не собираемся перегружать другие типы функций, которые не вызываются, или более сложные типы функций. То, что у нас было раньше, действительно все, что нам нужно.

Что нормально. По сути, это просто пример, когда Concepts не решила проблему, которой у нас не было. Но это интересный случай, когда мы (или, по крайней мере, я) продолжим использовать C ++ 17 SFINAE даже с Concepts TS.