提问人:Kevin 提问时间:9/24/2008 最后编辑:Jan SchultkeKevin 更新时间:10/9/2023 访问量:55735
如何删除相似的 const 和非 const 成员函数之间的代码重复?
How do I remove code duplication between similar const and non-const member functions?
问:
假设我有以下情况,我想将访问权限返回给内部成员:class X
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
这两个成员函数在大括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数出现维护问题。X::Z()
X::Z() const
有没有办法避免这种代码重复?
答:
是的,可以避免代码重复。您需要使用 const 成员函数来获得逻辑,并让非 const 成员函数调用 const 成员函数并将返回值重新转换为非常量引用(如果函数返回指针,则转换为指针):
class X
{
std::vector<Z> vecZ;
public:
const Z& z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.z(index) );
}
#endif
};
注意:重要的是,不要将逻辑放在非常量函数中,而让常量函数调用非常量函数 - 这可能会导致未定义的行为。原因是常量类实例被强制转换为非常量实例。非常量成员函数可能会意外修改类,C++ 标准状态将导致未定义的行为。
评论
这篇 DDJ 文章介绍了一种使用模板专用化的方法,该方法不需要使用const_cast。对于如此简单的功能,它确实不需要。
boost::any_cast(在某一时刻,它不再使用)使用 const 版本中的const_cast调用非 const 版本以避免重复。但是,您不能将 const 语义强加给 non-const 版本,因此您必须非常小心。
最后,只要两个代码段直接在彼此之上,一些代码重复是可以的。
评论
通常,需要 const 和非 const 版本的成员函数是 getter 和 setter。大多数情况下,它们是单行代码,因此代码重复不是问题。
评论
有关详细说明,请参阅第 23 页第 3 项“尽可能使用”中的标题“避免重复和非成员功能”,载于 Scott Meyers 的 Effective C++,3d ed,ISBN-13:9780321334879。const
const
const
这是 Meyers 的解决方案(简化):
struct C {
const char & get() const {
return c;
}
char & get() {
return const_cast<char &>(static_cast<const C &>(*this).get());
}
char c;
};
两个强制转换和函数调用可能很丑陋,但在非方法中是正确的,因为这意味着对象不是一开始就。(迈耶斯对此进行了深入的讨论。const
const
评论
get()const
get()
const_cast
std::as_const()
更好。
比 Meyers 更冗长一些,但我可能会这样做:
class X {
private:
// This method MUST NOT be called except from boilerplate accessors.
Z &_getZ(size_t index) const {
return something;
}
// boilerplate accessors
public:
Z &getZ(size_t index) { return _getZ(index); }
const Z &getZ(size_t index) const { return _getZ(index); }
};
private 方法有一个不良属性,即它为 const 实例返回一个非常量 Z&,这就是它是私有的原因。私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是“不能通过通过它获得的对它所具有的对象的引用来修改 const 对象”)。
请注意,注释是模式的一部分 - _getZ 的接口指定调用它永远无效(显然,除了访问器):无论如何,这样做没有任何好处,因为它要键入的字符增加了 1 个字符,并且不会导致更小或更快的代码。调用该方法等效于使用const_cast调用其中一个访问器,您也不想这样做。如果你担心错误变得明显(这是一个公平的目标),那么就称之为const_cast_getZ而不是_getZ。
顺便说一句,我很欣赏迈耶斯的解决方案。我对此没有哲学上的反对意见。不过,就我个人而言,我更喜欢一点点受控的重复,以及一种只能在某些严格控制的情况下调用的私有方法,而不是看起来像线路噪声的方法。选择你的毒药并坚持下去。
[编辑:Kevin 正确地指出,_getZ可能想要调用另一个方法(比如 generateZ),该方法与 getZ 相同。在这种情况下,_getZ将看到一个常量 Z&,并且必须在返回之前const_cast它。这仍然是安全的,因为样板访问器会监管所有内容,但它的安全并不是很明显。此外,如果你这样做了,然后把 generateZ 改成总是返回 const,那么你还需要把 getZ 改成总是返回 const,但编译器不会告诉你这样做。
关于编译器的后一点也适用于 Meyers 推荐的模式,但关于不明显const_cast的第一点则不然。所以总的来说,我认为如果_getZ需要一个const_cast来获得它的返回值,那么这个模式就会比 Meyers 的模式失去很多价值。由于与迈耶斯相比,它也遭受了劣势,我想在这种情况下我会改用他。从一个重构到另一个重构很容易 - 它不会影响类中的任何其他有效代码,因为只有无效代码和样板调用_getZ。
评论
something
_getZ()
_getZ()
something
const Z&
Z&
something
const_cast
将逻辑移动到私有方法中,并且只在 getter 中执行“获取引用和返回”的东西怎么样?实际上,我会对一个简单的 getter 函数中的 static 和 const 强制转换感到困惑,除非在极少数情况下,否则我认为这很丑陋!
评论
您也可以使用模板来解决这个问题。这个解决方案有点丑陋(但丑陋隐藏在 .cpp 文件中),但它确实提供了编译器的常量检查,并且没有代码重复。
.h 文件:
#include <vector>
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
const std::vector<Z>& GetVector() const { return vecZ; }
std::vector<Z>& GetVector() { return vecZ; }
Z& GetZ( size_t index );
const Z& GetZ( size_t index ) const;
};
.cpp 文件:
#include "constnonconst.h"
template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
// ... massive amounts of code ...
// Note you may only use methods of X here that are
// available in both const and non-const varieties.
Child& ret = parent->GetVector()[index];
// ... even more code ...
return ret;
}
Z& X::GetZ( size_t index )
{
return GetZImpl< X*, Z >( this, index );
}
const Z& X::GetZ( size_t index ) const
{
return GetZImpl< const X*, const Z >( this, index );
}
我可以看到的主要缺点是,由于该方法的所有复杂实现都在全局函数中,因此您要么需要使用上面的 GetVector() 等公共方法获取 X 的成员(其中始终需要常量和非恒量版本),要么您可以将此函数设为朋友。但我不喜欢朋友。
[编辑:删除了测试期间添加的不需要的 cstdio。
评论
const_cast
我认为Scott Meyers的解决方案可以通过使用tempate帮助函数在C++11中得到改进。这使得意图更加明显,并且可以重用于许多其他 getter。
template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference
template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
TObj const* obj,
TConstReturn (TObj::* memFun)(TArgs...) const,
TArgs&&... args) {
return const_cast<typename NonConst<TConstReturn>::type>(
(obj->*memFun)(std::forward<TArgs>(args)...));
}
此帮助程序函数可以按以下方式使用。
struct T {
int arr[100];
int const& getElement(size_t i) const{
return arr[i];
}
int& getElement(size_t i) {
return likeConstVersion(this, &T::getElement, i);
}
};
第一个参数始终是 this-pointer。第二个是指向要调用的成员函数的指针。之后,可以传递任意数量的附加参数,以便将它们转发到函数。 由于可变参数模板,这需要 C++11。
评论
std::remove_bottom_const
std::remove_const
const_cast
getElement
mpl::conditional
iterator
constiterator
std::remove_const<int const&>
int const &
const
NonConst<T>
std::remove_bottom_const
const
NonConst<T>
std::remove_bottom_const<int const&>::type
int&
getElement
likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
很好的问题和很好的答案。我还有另一种解决方案,它不使用强制转换:
class X {
private:
std::vector<Z> v;
template<typename InstanceType>
static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
// massive amounts of code for validating index
// the instance variable has to be used to access class members
return instance.v[i];
}
public:
const Z& get(std::size_t i) const {
return get(*this, i);
}
Z& get(std::size_t i) {
return get(*this, i);
}
};
但是,它有一个丑陋之处,即需要一个静态成员,并且需要使用其中的变量。instance
我没有考虑这个解决方案的所有可能的(负面)影响。如果有的话,请告诉我。
评论
auto get(std::size_t i) -> auto(const), auto(&&)
auto foo() -> auto(const), auto(&&) = delete;
this
template< typename T > auto myfunction(T this, t args) -> decltype(ident)
T
T
const_cast
iterator
const_iterator
static
为了添加到 jwfearn 和 kevin 提供的解决方案中,以下是函数返回 shared_ptr 时的相应解决方案:
struct C {
shared_ptr<const char> get() const {
return c;
}
shared_ptr<char> get() {
return const_pointer_cast<char>(static_cast<const C &>(*this).get());
}
shared_ptr<char> c;
};
我这样做是为了一个朋友,他理所当然地证明了使用......不知道我可能会做这样的事情(不是很优雅):const_cast
#include <iostream>
class MyClass
{
public:
int getI()
{
std::cout << "non-const getter" << std::endl;
return privateGetI<MyClass, int>(*this);
}
const int getI() const
{
std::cout << "const getter" << std::endl;
return privateGetI<const MyClass, const int>(*this);
}
private:
template <class C, typename T>
static T privateGetI(C c)
{
//do my stuff
return c._i;
}
int _i;
};
int main()
{
const MyClass myConstClass = MyClass();
myConstClass.getI();
MyClass myNonConstClass;
myNonConstClass.getI();
return 0;
}
我建议使用私有助手静态函数模板,如下所示:
class X
{
std::vector<Z> vecZ;
// ReturnType is explicitly 'Z&' or 'const Z&'
// ThisType is deduced to be 'X' or 'const X'
template <typename ReturnType, typename ThisType>
static ReturnType Z_impl(ThisType& self, size_t index)
{
// massive amounts of code for validating index
ReturnType ret = self.vecZ[index];
// even more code for determining, blah, blah...
return ret;
}
public:
Z& Z(size_t index)
{
return Z_impl<Z&>(*this, index);
}
const Z& Z(size_t index) const
{
return Z_impl<const Z&>(*this, index);
}
};
没有找到我想要的东西,所以我自己卷了几个......
这个有点啰嗦,但优点是可以同时处理许多同名(和返回类型)的重载方法:
struct C {
int x[10];
int const* getp() const { return x; }
int const* getp(int i) const { return &x[i]; }
int const* getp(int* p) const { return &x[*p]; }
int const& getr() const { return x[0]; }
int const& getr(int i) const { return x[i]; }
int const& getr(int* p) const { return x[*p]; }
template<typename... Ts>
auto* getp(Ts... args) {
auto const* p = this;
return const_cast<int*>(p->getp(args...));
}
template<typename... Ts>
auto& getr(Ts... args) {
auto const* p = this;
return const_cast<int&>(p->getr(args...));
}
};
如果每个名称只有一个方法,但仍然有很多方法可以复制,那么您可能更喜欢这个:const
template<typename T, typename... Ts>
auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
return const_cast<T*>((this->*f)(args...));
}
int* getp_i(int i) { return pwrap(&C::getp_i, i); }
int* getp_p(int* p) { return pwrap(&C::getp_p, p); }
不幸的是,一旦你开始重载名称,这就会崩溃(函数指针参数的参数列表在这一点上似乎尚未解析,因此它找不到函数参数的匹配项)。尽管您也可以通过模板来摆脱这种情况:
template<typename... Ts>
auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }
但是,该方法的引用参数无法与模板的明显按值参数匹配,并且会中断。不知道为什么。原因如下。const
使用预处理器是作弊吗?
struct A {
#define GETTER_CORE_CODE \
/* line 1 of getter code */ \
/* line 2 of getter code */ \
/* .....etc............. */ \
/* line n of getter code */
// ^ NOTE: line continuation char '\' on all lines but the last
B& get() {
GETTER_CORE_CODE
}
const B& get() const {
GETTER_CORE_CODE
}
#undef GETTER_CORE_CODE
};
它不像模板或演员表那样花哨,但它确实使你的意图(“这两个功能是相同的”)非常明确。
评论
C++17 更新了这个问题的最佳答案:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
这样做的优点是:
- 很明显发生了什么
- 具有最小的代码开销 -- 它适合一行
- 很难出错(只能偶然抛弃,但是一个罕见的限定词)
volatile
volatile
如果你想走完整的演绎路线,那么这可以通过一个辅助函数来实现
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
现在你甚至不能搞砸,用法看起来像volatile
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) f() {
return as_mutable(std::as_const(*this).f());
}
评论
f()
T
T&
f()
T
const
shared_ptr
as_mutable_ptr
as_mutable
shared_ptr
std::const_pointer_cast
const_cast
T const*
T const* const&&
T const* const&
T const*
对于那些(像我一样)的人
- 使用 C++17
- 想要添加最少的样板/重复,并且
- 不介意使用宏(在等待元类时......
这是另一种观点:
#include <utility>
#include <type_traits>
template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};
#define NON_CONST(func) \
template <typename... T> auto func(T&&... a) \
-> typename NonConst<decltype(func(std::forward<T>(a)...))>::type \
{ \
return const_cast<decltype(func(std::forward<T>(a)...))>( \
std::as_const(*this).func(std::forward<T>(a)...)); \
}
它基本上是来自@Pait、@DavidStone和@sh1的答案的混合体(编辑:以及@cdhowie的改进)。它添加到表中的是,您只需要一行额外的代码,该代码仅命名函数(但没有参数或返回类型重复):
class X
{
const Z& get(size_t index) const { ... }
NON_CONST(get)
};
注意:gcc 在 8.1 之前无法编译,clang-5 及更高版本以及 MSVC-19 都很高兴(根据编译器资源管理器)。
评论
decltype()
std::forward
get()
NON_CONST
const_cast
decltype(func(a...))
decltype(func(std::forward<T>(a)...))
X::get
令我惊讶的是,有这么多不同的答案,但几乎所有的答案都依赖于沉重的模板魔法。模板功能强大,但有时宏在简洁性上胜过它们。最大的多功能性通常是通过两者的结合来实现的。
我编写了一个宏,可以将其放置在非 const 函数中以调用 const 函数。FROM_CONST_OVERLOAD()
用法示例:
class MyClass
{
private:
std::vector<std::string> data = {"str", "x"};
public:
// Works for references
const std::string& GetRef(std::size_t index) const
{
return data[index];
}
std::string& GetRef(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetRef(index) );
}
// Works for pointers
const std::string* GetPtr(std::size_t index) const
{
return &data[index];
}
std::string* GetPtr(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetPtr(index) );
}
};
简单且可重用的实现:
template <typename T>
T& WithoutConst(const T& ref)
{
return const_cast<T&>(ref);
}
template <typename T>
T* WithoutConst(const T* ptr)
{
return const_cast<T*>(ptr);
}
template <typename T>
const T* WithConst(T* ptr)
{
return ptr;
}
#define FROM_CONST_OVERLOAD(FunctionCall) \
WithoutConst(WithConst(this)->FunctionCall)
解释:
正如许多答案中所发布的,避免在非常量成员函数中重复代码的典型模式是这样的:
return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );
使用类型推断可以避免很多这种样板。首先,可以封装在 中,它推断其参数的类型并删除 const-qualifier。其次,可以使用类似的方法来对指针进行 const-限定,从而可以调用 const-重载方法。const_cast
WithoutConst()
WithConst()
this
剩下的就是一个简单的宏,它用正确的限定值作为调用的前缀,并从结果中删除 const。由于宏中使用的表达式几乎总是具有 1:1 转发参数的简单函数调用,因此宏的缺点(例如多重计算)不会发挥作用。也可以使用省略号,但不需要,因为逗号(作为参数分隔符)出现在括号内。this->
__VA_ARGS__
此方法有几个优点:
- 最小而自然的语法 -- 只需将调用包装起来
FROM_CONST_OVERLOAD( )
- 无需额外的成员功能
- 与 C++98 兼容
- 实现简单,无模板元编程,零依赖
- 可扩展:可以添加其他常量关系(如、等)。为此,只需重载相应的类型即可。
const_iterator
std::shared_ptr<const T>
WithoutConst()
局限性:此解决方案针对非 const 重载与 const 重载完全相同的场景进行了优化,因此参数可以 1:1 转发。如果你的逻辑不同,并且你没有通过 调用 const 版本,你可以考虑其他方法。this->Method(args)
我想出了一个宏,可以自动生成成对的常量/非常量函数。
class A
{
int x;
public:
MAYBE_CONST(
CV int &GetX() CV {return x;}
CV int &GetY() CV {return y;}
)
// Equivalent to:
// int &GetX() {return x;}
// int &GetY() {return y;}
// const int &GetX() const {return x;}
// const int &GetY() const {return y;}
};
有关实现,请参阅答案的末尾。
的参数是重复的。在第一个副本中,被替换为 nothing;在第二个副本中,它被替换为 .MAYBE_CONST
CV
const
在宏参数中可以出现的次数没有限制。CV
不过有一点不便。如果出现在括号内,则此对括号必须以 :CV
CV_IN
// Doesn't work
MAYBE_CONST( CV int &foo(CV int &); )
// Works, expands to
// int &foo( int &);
// const int &foo(const int &);
MAYBE_CONST( CV int &foo CV_IN(CV int &); )
实现:
#define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
#define CV )(IMPL_CV_identity,
#define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,
#define IMPL_CV_null(...)
#define IMPL_CV_identity(...) __VA_ARGS__
#define IMPL_CV_p_open(...) (
#define IMPL_CV_p_close(...) )
#define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq
#define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__
#define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
#define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)
#define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
#define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)
不支持的 C++ 之前的实现:CV_IN
#define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
#define CV ))((
#define IMPL_MC(seq) \
IMPL_MC_end(IMPL_MC_a seq) \
IMPL_MC_end(IMPL_MC_const_0 seq)
#define IMPL_MC_identity(...) __VA_ARGS__
#define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
#define IMPL_MC_end_(...) __VA_ARGS__##_end
#define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
#define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
#define IMPL_MC_a_end
#define IMPL_MC_b_end
#define IMPL_MC_const_0(elem) IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
#define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
#define IMPL_MC_const_a_end
#define IMPL_MC_const_b_end
如果您不喜欢常量转换,我使用另一个答案建议的 C++17 版本的模板静态辅助函数,以及可选的 SFINAE 测试。
#include <type_traits>
#define REQUIRES(...) class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B) REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )
class Foobar {
private:
int something;
template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
static auto& _getSomething(FOOBAR& self, int index) {
// big, non-trivial chunk of code...
return self.something;
}
public:
auto& getSomething(int index) { return _getSomething(*this, index); }
auto& getSomething(int index) const { return _getSomething(*this, index); }
};
完整版: https://godbolt.org/z/mMK4r3
虽然这里的大多数答案都建议使用 ,但 CppCoreGuidelines 有一节是关于这一点的:const_cast
相反,更喜欢共享实现。通常,您可以让非常量函数调用常量函数。但是,当存在复杂的逻辑时,这可能会导致以下模式仍诉诸const_cast:
class Foo {
public:
// not great, non-const calls const version but resorts to const_cast
Bar& get_bar()
{
return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
}
const Bar& get_bar() const
{
/* the complex logic around getting a const reference to my_bar */
}
private:
Bar my_bar;
};
尽管此模式在正确应用时是安全的,因为 调用者必须有一个非常量对象,这并不理想 因为安全性很难作为检查规则自动执行。
相反,更愿意将通用代码放在一个通用的辅助函数中—— 并使其成为模板,以便它推断出 const。这不使用任何 const_cast完全没有:
class Foo {
public: // good
Bar& get_bar() { return get_bar_impl(*this); }
const Bar& get_bar() const { return get_bar_impl(*this); }
private:
Bar my_bar;
template<class T> // good, deduces whether T is const or non-const
static auto& get_bar_impl(T& t)
{ /* the complex logic around getting a possibly-const reference to my_bar */ }
};
注意:不要在模板中执行大型非依赖性工作,这会导致代码膨胀。例如,进一步的改进是,如果全部或部分get_bar_impl可以不依赖,并分解为一个通用的非模板函数,则代码大小可能会大幅减少。
由于显式对象参数,C++23 更新了这个问题的最佳答案。
struct s {
auto && f(this auto && self) {
// all the common code goes here
}
};
单个函数模板可作为普通成员函数调用,并为您推断正确的引用类型。没有出错的投射,没有为概念上是一回事的东西编写多个函数。
注意:此功能由 P0847 添加:推断此功能。
评论
volatile
const
const
评论