учитывая следующий код:
// g++ main.cpp -std=c++23
#include <type_traits>
using namespace std;
template<typename P>
concept pointer_like = is_pointer_v<P> || requires (P p) { p.operator ->(); };
template<pointer_like P>
constexpr auto func(P p)
noexcept(is_pointer_v<P> || noexcept(p.operator ->())) // error: 'int *' doesn't have 'operator ->'
{
if constexpr (is_pointer_v<P>)
return p;
else
return p.operator ->();
}
int main() {
int i = 0;
auto pi = func(&i); // error from here
}
https://godbolt.org/z/Gqq3W4o4h
Кажется, короткое замыкание не выполняется в выражении noexcept
:
noexcept(is_pointer_v<P> || noexcept(p.operator ->()))
Мои ожидания:
is_pointer_v<P>
истинно, поэтому operator ->
не проверяется;operator ->
.Это что-то, чего не хватает в стандарте C++?
Каждое подвыражение в noexcept
должно быть действительным индивидуально/отдельно.
Вы можете использовать requires
для достижения желаемого эффекта/результата, как показано ниже:
template<pointer_like P> constexpr auto func(P p)
noexcept(requires { requires is_pointer_v<P> || noexcept(p.operator ->()); })
{
if constexpr (is_pointer_v<P>)
return p;
else
return p.operator ->();
}
Я не думаю, что это работает, потому что requires
означает, что содержимое {...}
действительно, но НЕ обязательно ИСТИНА. Пожалуйста, посмотрите тестовый код: godbolt.org/z/ea7Wja6M5.
@DoZerg Вы используете старую демо-ссылку. Это работает, когда вы используете демо-версию новой ссылки. Обратите внимание на использование requires requires
для выполнения этой работы, которого нет в вашей демо-версии.
Только что заметил! Спасибо за ответ!
@DoZerg Пожалуйста :) Кстати, ваш опубликованный ответ на самом деле такой же, как и мой. Вы явно используете дополнительные круглые скобки, которые здесь не нужны, но могут быть использованы.
Выражение в noexcept
должно быть правильно сформулировано. Сокращение логических операторов применимо только к оценке операндов, а не к их правильности.
Проблему можно решить с помощью другого уровня косвенности, который правильно обрабатывает неправильно сформированные операнды:
template<pointer_like P>
consteval auto noexcept_check()
{
if constexpr (is_pointer_v<P>)
return true;
else
return noexcept(P{}.operator ->());
}
и тогда объявление func
просто:
template<pointer_like P>
constexpr auto func(P p)
noexcept(noexcept_check<P>())
Спасибо за ответ! Это «стандартный» способ, который я много видел в STL, хотя мне интересно, существует ли более короткий подход.
@DoZerg Короче говоря, вы имеете в виду отсутствие написания отдельной функции? Вы можете использовать эту технику с requires
, как показано в другом ответе. Лично я предпочитаю именованную функцию, поскольку ее легче читать и более четко передать намерение (конечно, и то, и другое определенно субъективно), и ее можно использовать повторно.
Обратите внимание, что фиксированным является только порядок вычисления операндов для
||
. Оба операнда||
должны быть допустимыми выражениями.