given the follow code:
// 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
It seems short-circuit is not performed in the noexcept expression:
noexcept(is_pointer_v<P> || noexcept(p.operator ->()))
My expectation is:
is_pointer_v<P> is true so operator -> is not checked;operator -> is valid.Is it something C++ standard is missing?
The expression in the noexcept needs to be well-formed. The short-circuiting of logical operators only applies to the evaluation of the operands, not to their well-formedness.
The problem can be resolved with another level of indirection that correctly handles the ill-formed operands:
template<pointer_like P>
consteval auto noexcept_check()
{
if constexpr (is_pointer_v<P>)
return true;
else
return noexcept(P{}.operator ->());
}
and then the declaration of func is simply:
template<pointer_like P>
constexpr auto func(P p)
noexcept(noexcept_check<P>())
demo
Each sub-expression in the noexcept must be valid individually/separately.
You can use requires to achieve the desired effect/result as shown below:
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 ->();
}
Working demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With