Considering the code example shown below, is decltype(h{}.t());
a legal expression? Which specific rule in the C++20 standard either allows or forbids this?
考虑到下面显示的代码示例,decltype(h{}.t());是合法的表达式吗?C++20标准中的哪个特定规则允许或禁止这样做?
struct d;
struct h { static auto t() -> d; };
using a = decltype(h::t()); // all ok
using b = decltype(decltype(h{})::t()); // all ok
using c = decltype(h{}.t()); // clang ok, gcc ok, msvc nope
Live example
现场示例
The error message produced by MSVC:
MSVC产生的错误消息:
<source>(6): error C2027: use of undefined type 'd'
<source>(1): note: see declaration of 'd'
Having stumbled upon yet another bug with MSVC, I've actually discovered a workaround for MSVC's deviating behavior. By simply providing a user-defined defaulted default constructor for type h
, MSVC no longer complains about d
being undefined.
在偶然发现了MSVC的另一个错误之后,我实际上已经发现了一种解决MSVC偏离行为的方法。只需为类型h提供一个用户定义的默认构造函数,MSVC就不再抱怨d未定义。
struct d;
struct h {
static auto t() -> d;
h() = default;
};
using c = decltype(h{}.t()); // all ok
However, the problem still seems to persist when skipping use of h
's constructor, like with the use of std::declval
for example (as in: decltype(std::declval<h>().t());
). Also, be aware that by providing a user-defined default constructor, even when defaulted, this will cause h
to no longer be considered an aggregate type.
但是,当跳过h的构造函数的使用时,问题似乎仍然存在,例如使用std::declval(如:decltype(std::declval
().t());)。另外,请注意,通过提供用户定义的默认构造函数,即使在默认情况下,这也会导致h不再被视为聚合类型。
更多回答
Does std::declval<h>().t()
behave any different in MSVC?
Std::decval().t()在MSVC中的行为有什么不同吗?
@StoryTeller-UnslanderMonica, fails with the same error, it wants you to define d
.
@storytaler-UnslanderMonica失败,并返回相同的错误,它希望您定义d。
@Blindy This might be very useful in generic code. As a simple example consider a function that returns/uses a container size. A member function size()
could be a static one for a compile-time-sized container and a non-static one if container size is determined at run-time.
@bllindy这在泛型代码中可能非常有用。作为一个简单的例子,考虑一个返回/使用容器大小的函数。对于编译时大小的容器,成员函数SIZE()可以是静态的,如果容器大小是在运行时确定的,则可以是非静态的。
优秀答案推荐
It is explicitly permitted for the type of a prvalue expression operand to decltype
to be incomplete. Temporary materialization is exceptionally not applied in this circumstance, so that a destructor of d
isn't necessary either (which would require d
to be complete to lookup the destructor). So d
doesn't need to be complete. For reference to the standard see [dcl.type.decltype]/2.
显式允许PrValue表达式操作数的类型dectype不完整。在这种情况下,临时物化例外地不适用,因此也不需要d的析构函数(这将要求d完成对析构函数的查找)。所以d不需要是完整的。关于该标准的参考见[dcl.type.decltype]/2。
The function t
may also be undefined, because use in a decltype
operand is not use in a potentially-evaluated expression and therefore not an odr-use.
函数t也可以是未定义的,因为在decltype操作数中使用不会在可能求值的表达式中使用,因此不会使用ODR。
Constructing a h
object with h{}
also doesn't demand either t
to be defined or d
to be complete.
使用h{}构造h对象也不需要定义t或完成d。
The standard even includes a very similar example (see link above).
该标准甚至包括一个非常相似的例子(参见上面的链接)。
So MSVC is wrong here.
因此,MSVC在这一点上是错误的。
更多回答
It seems that in order to call t
by using the member access operator .
, h
will have to be materialized, and this shouldn't be a problem since h
is complete. If, for example, h
's destructor is marked as deleted, and when trying decltype([]<typename T = h>() -> T {}().t());
, all compilers reject this as h
has to be materialized which invokes its deleted destructor implicitly.
似乎为了通过使用成员访问操作符.来调用t,h必须被具体化,这应该不是问题,因为h是完整的。例如,如果h的析构函数被标记为已删除,并且在尝试dectype([]()->T{}().T());时,所有编译器都会拒绝此操作,因为h必须被物化,这将隐式调用其已删除的析构函数。
So, since h
can be materialized in the provided example, the use of t
shouldn't be different from any other type of declared but undefined function, which I think is best described in [dcl.type.decltype]/note-2
: "... a class type is not instantiated as a result of being the type of a function call ... so the usual reasons for requiring a complete type do not apply".
因此,由于h可以在所提供的示例中实现,所以t的使用不应该与任何其他类型的已声明但未定义的函数不同,我认为在[dcl.type.decltype]/note-2中对此进行了最好的描述:“……类类型不会作为函数调用的类型而被实例化……所以要求完整类型的通常原因并不适用”。
"all compilers reject this as h has to be materialized which invokes its deleted destructor implicitly": A temporary object of type h
has to be materialized. A materialized temporary object is always destroyed at the end of the full-expression, which here is the whole decltype
operand, so the destructor of h
is used and therefore must not be deleted, yes.
“所有编译器都拒绝这样做,因为h必须被物化,这将隐式调用其已删除的析构函数”:必须物化h类型的临时对象。物化的临时对象总是在完整表达式的末尾销毁,这里是整个dectype操作数,所以使用h的析构函数,因此不能删除,是的。
@303 Yes, the part above the note is the normative equivalent that says that no materialization, and therefore no destructor invocation, happens for the d
prvalue (which is the contentious part).
@303是的,注释上面的部分是规范上的等价物,说明对于dPrValue(这是有争议的部分),没有物化发生,因此也没有析构函数调用。
After doing some tests with mutable
subojects of a constexpr
variable, which led me to the discovery of (yet) another bug that seems to point to the root cause of MSVC's deviating behavior, I actually stumbled upon a rather surprising workaround for MSVC (refer to my original post).
在使用conexpr变量的可变子对象进行了一些测试后,我发现了另一个错误,它似乎指向了MSVC偏离行为的根本原因,实际上我偶然发现了一个相当令人惊讶的MSVC解决方法(请参阅我最初的帖子)。
我是一名优秀的程序员,十分优秀!