Так что разрешение перегрузки здесь немного тупо.
Это не говорит: «Хорошо, если не-1003 * apply
работает, я никогда не позвоню const apply
, поэтому я не буду беспокоиться об этом».
Вместо этого разрешение перегрузки обнуляет каждого возможного кандидата. Затем он устраняет те, которые терпят неудачу замещения. Только тогда он заказывает кандидатов и выбирает одного.
Таким образом, оба из них F
подставляются в них:
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
Я снял их тела.
Теперь, что происходит, когда вы передаете лямбда-тип F
этим?
Ну, лямбды имеют эквивалент auto
возвращаемого типа. Чтобы выяснить фактический тип возвращаемого значения при передаче чего-либо, компилятор должен проверить тело лямбды.
И SFINAE не работает при проверке органов функций (или лямбд). Это сделано для того, чтобы упростить работу компилятора (поскольку SFINAE очень сложен для компиляторов, поскольку им приходится компилировать произвольный код и сталкиваться с произвольными ошибками, а затем откатывать его назад - огромный барьер).
Мы можем избежать мгновенного образования лямбда-тела с помощью этого:
[](auto &&value) -> void { /* ... */ }
после этого обе перегрузки apply
:
template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)
template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const
может иметь оценочное возвращаемое значение (это просто void
), и мы получаем:
template <typename F=$lambda$>
void apply(F &&f)
template <typename F=$lambda$>
void apply(F &&f) const
Теперь обратите внимание, что apply const
все еще существует. Если вы позвоните apply const
, вы получите серьезную ошибку, вызванную созданием этого лямбда-тела.
Если вы хотите, чтобы сама лямбда была дружественной по отношению к SFINAE, вам нужно сделать следующее:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
[](auto &&value) RETURNS(++value)
обратите внимание, что эта лямбда немного отличается, так как возвращает ссылку на значение. Мы можем избежать этого с помощью:
[](auto &&value) RETURNS((void)++value)
и теперь лямбда-версия дружественна к SFINAE и ведет себя так же, как ваша исходная лямбда и ваша оригинальная программа компилируется как есть с этим изменением.
Побочным эффектом этого является то, что применение не const
теперь исключено из разрешения перегрузки SFINAE. Что делает его дружелюбным по отношению к СФИНАМ.
Было предложение взять RETURNS
и переименовать его =>
, но в последний раз я проверял, что он не был принят для c ++ 20 .