提问人:felps321 提问时间:8/28/2023 更新时间:8/29/2023 访问量:120
为什么 const 成员函数模板是针对非 const 对象实例化的?
Why is const member function template is instantiated for non-const object?
问:
我偶然发现了一个问题,即为非常量对象实例化了 const 成员函数模板,这会导致编译错误。 下面是可重现的示例:
#include <cstdio>
#include <type_traits>
struct S {
using Data = int;
Data data{};
template <typename F> requires std::is_invocable_v<F, Data&>
void func(F)
{
puts("S::func(F)");
}
template <typename F> requires std::is_invocable_v<F, const Data&>
void func(F) const
{
puts("S::func(F) const");
}
};
struct FuncObj {
template <typename T>
auto operator()(T&)
{
// imaginary code that uses T assuming that it's non-const
static_assert(!std::is_const_v<T>);
}
};
int main()
{
S s;
s.func(FuncObj());
}
编译此代码时,出现“静态断言失败”错误。
现在,我确实知道这个错误是由存在和需要的返回类型引起的
要推导,因此需要实例化模板函数体,我从这个问题中了解到这一点:为什么 std::is_invocable 不与自动推导的返回类型(例如通用 lambda)一起使用模板化 operator()。FuncObj::operator()
auto
我确实理解这部分。但是我不明白的是,为什么编译器在不是 const 对象时甚至考虑 const 成员函数 ()?
如果编译器只是推导/实例化(非常量成员函数),则不会有错误。
此外,如果我在函数中使用相同的条件,而不是(或 SFINAE),
错误消失了:S::func(F) const
S s
S::func(F)
requires
void func(F) const
static_assert
template <typename F>
void func(F) const // requires std::is_invocable_v<F, const Data&>
{
static_assert(std::is_invocable_v<F, const Data&>);
puts("S::func(F) const");
}
这似乎非常不直观,我想不出任何理由来解释为什么编译器会在有非常量函数时尝试成员函数的版本?
C++ 标准中是否有任何地方对其进行了解释,以便我能够理解这一点?const
答:
首先,您已经正确地理解了 的两个重载都是成员函数调用的候选者。func
s.func(FuncObj());
编译器必须检查第二个重载是否为 true,这涉及检查格式是否正确,其中 is 的类型为 。 存在并不妨碍 A 绑定到它。它只是意味着推导为 .目前还没有任何东西被淘汰出重载集。std::is_invocable_v<F, const Data&>
F(D)
D
const Data&
D
const Data&
T&
T
const Data
因为是 ,并且有推导的返回类型,所以需要实例化它。
在实例化期间:F
FuncObj
FuncObj::operator()
template <typename T> // T = const Data
auto operator()(T&) // T& = const Data&
{
// std::is_const_v<const Data> is true, so this static assertion fails.
// The program is ill-formed because a static_assertion has failed.
static_assert(!std::is_const_v<T>);
}
编译器实际上会准确地告诉你这个错误发生在哪里:
<source>:27:23: error: static assertion failed due to requirement '!std::is_const_v<const int>'
27 | static_assert(!std::is_const_v<T>);
| ^~~~~~~~~~~~~~~~~~~
[...]
<source>:15:36: note: while substituting template arguments into constraint expression here
15 | template <typename F> requires std::is_invocable_v<F, const Data&>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:34:7: note: while checking constraint satisfaction for template 'func<FuncObj>' required here
34 | s.func(FuncObj());
| ^~~~
通常,对 SFINAE 不友好,函数体中的任何断言失败都会使程序格式不正确,而不是将候选者从重载集中剔除。
它被称为“替换失败不是错误”,而不是“实例化失败不是错误”。static_assert
正如你所指出的,你可以这样写:
template <typename T> requires (!std::is_const_v<T>)
这将编译时没有错误。如果您使用而不是推导的返回类型,那也很好,因为检查表达式有效性不会涉及实例化函数模板。void
评论
auto
-> void
requires
static_assert
const
const
[](Data&) {}
auto
const Data&
T
std::unique_ptr
T
const std::unique_ptr&
std::move
评论
std::is_invocable_v
需要做过载解决。否则它将如何给出答案?由于必须实例化这种手段...剩下的你就知道了。operator()
operator()
auto