模板代码中不完整的类型 [英] Incomplete types in template code

查看:312
本文介绍了模板代码中不完整的类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有两种类型(完整和不完整):

  struct CompleteType {}; 

struct IncompleteType;

我们还有模板代码:

  #include< type_traits> 

template< typename = X(T)>
struct Test:std :: false_type {};

模板<>
struct Test< T> :std :: true_type {};

T 可以 CompleteType IncompleteType 此处和 X(T)可以 T decltype(T()) decltype(T {})假设 X(T)是一个宏)。



此代码以下列方式使用:

  std :: cout< std :: boolalpha< Test<> :: value<< std :: endl; 

下面你可以看到不同的编译器如何处理这样的代码:






clang 3.4

  X(T)\ T CompleteType IncompleteType 
T true true
decltype(T())true ---(1,2)
decltype(T {})true --- 1,2)




  1. 错误:对于不完全类型的模板类声明( decltype(T()),无效使用不完全类型'IncompleteType'没有使用测试<>< code> code> code> decltype(T {}),但不是简单 T :: value


  2. 错误:类模板的模板参数太少Test







.1



  X(T)\ T CompleteType IncompleteType 
T true true
decltype(T())true true
decltype(T {})true true




b
$ b

vc ++ 18.00.21005.1

  \\ T CompleteType IncompleteType 
T true true
decltype(T())true ---(1)
decltype(T {})true ---(2)




  1. 错误C2514:'IncompleteType':class has no构造函数


  2. 错误C2440:'< function-style-cast& initializer-list'to'IncompleteType'源或目标具有不完整类型







什么编译器根据标准行为?请注意,简单的字符串,如 std :: cout< typeid(X(IncompleteType))。name()<< std :: endl; 不会在所有编译器上为 X vc ++ p>我相信Clang和MSVC的行为在这种情况下与标准一致。我想GCC在这里有一点捷径。



我们先把几个事实放在表格上。 decltype 表达式的操作数是所谓的未经赋值的操作数,由于它们最终从不被评估的事实,它们被有点不同地对待。



特别地,对完成类型的要求较少。基本上,如果你有任何临时对象(作为表达式中涉及的函数或运算符的参数或返回值),它们不需要是完整的(见第5.2.2 / 11和7.1.6.2/5节)。但这只是提升了你不能声明一个不完整类型的对象的通常限制,但它不提升对不完全类型的其他限制,这是你不能调用一个不完整类型的成员函数。



表达式 decltype(T()) decltype T 是不完整的,必须查找 T类型的构造函数。 ,因为它是该类的(特殊)成员函数。这只是一个事实,它是一个构造函数调用,创建一个歧义(即,它只是创建一个临时对象?或者它是调用构造函数?)。如果是任何其他成员职能,就不会有辩论。幸运的是,标准确实解决了这个争论:


12.2 / 1



当临时对象的创建未被评估(条款
5)或以其他方式避免(12.8)时,所有的语义限制将
被尊重,就像临时对象已被创建并且随后
被销毁。 [注意:即使没有调用析构函数或
copy / move构造函数,所有的语义限制,如
辅助功能(第11章)和函数是否被删除
.3),应予以满足。然而,在用作decltype-specifier(5.2.2)的操作数的
函数调用的特殊情况下,没有引入
临时,因此前述内容不适用于
prvalue的任何这样的函数调用。 - end note]


最后一句可能有点混乱,但只适用于函数调用的返回值。换句话说,如果你有 T f(); 函数,并且你声明了 decltype(f())那么 T 不需要是完整的,或者是否有可用的和可访问的构造函数/析构函数的语义检查。



事实上,这整个问题正是为什么有一个 std :: declval 实用程序,因为当你不能使用 decltype ()),你可以使用 decltype(std :: declval< T>()) / code>只是一个(fake)函数,它返回类型 T 的prvalue。但是,当然, declat 意在用于较小的情况,例如 decltype(f(std :: declval< T> )其中 f 将是一个类型为 T 的对象的函数。和 declat 不要求类型完成(见第20.2.4节)。这基本上是你解决这个问题的方式。



因此,就GCC的行为而言,我相信它需要一个捷径,因为它试图找出 T() T {} 的类型。我认为,一旦GCC发现 T 引用一个类型名称(不是一个函数名称),它推断这是一个构造函数调用,因此,无论什么查找发现作为实际的构造函数被调用,返回类型将 T (严格来说,构造函数没有返回类型,但你明白我什么意思)。这里的一点是,这可能是一个未经评估的表达式中有用(更快)的捷径。



如果GCC允许 CompleteType ,那么这是不符合标准的行为。构造函数被删除或私有,那么这也与上面引用的标准通过直接相矛盾。


请注意,简单的字符串,如 std :: cout<< typeid(X(IncompleteType))。name()<< std :: endl; 不会在所有编译器上编译X的所有变体(除了vc ++和X(T)== T)。


这是预期的(除了MSVC和X(T)== T)。 typeid sizeof 运算符类似于 decltype 感觉他们的操作数是未评估的,但是,他们都有额外的要求,结果表达式的类型必须是一个完整的类型。可以设想,编译器可以为不完整的类型(或至少使用部分类型信息)解析 typeid ,但是标准需要一个完整的类型,这样编译器不会必须这样做。我想这是MSVC正在做的。



所以,在这种情况下, T() c $ c> t {} 失败的原因与 decltype (正如我刚才解释的)相同, > X(T)== T case失败,因为 typeid 需要一个完整的类型(但MSVC设法解除这个要求)。在GCC上,由于 typeid 需要所有 X(T)案例的完整类型在 sizeof typeid 的情况下,快捷方式GCC不会影响结果。



因此,总而言之,我认为Clang是三个标准中最标准的(不使用快捷方式或进行扩展)。


Suppose we have two types (complete and incomplete):

struct CompleteType{};

struct IncompleteType;

Also we have template code:

#include <type_traits>

template <typename = X(T)>
struct Test : std::false_type {};

template <>
struct Test<T> : std::true_type {};

T can be CompleteType or IncompleteType here and X(T) can be T, decltype(T()) or decltype(T{}) (suppose X(T) is a macro).

This code is used in the following manner:

std::cout << std::boolalpha << Test<>::value << std::endl;

Below you can see how different compilers deal with such code:


clang 3.4

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          --- (1, 2)
decltype(T{})  true          --- (1, 2)

  1. error: invalid use of incomplete type 'IncompleteType' is given even on template class declarations with incomplete types (both for decltype(T()) and decltype(T{}), but not for simple T) without using Test<>::value in the code.

  2. error: too few template arguments for class template 'Test'


g++ 4.8.1

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          true      
decltype(T{})  true          true      


vc++ 18.00.21005.1

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          --- (1)   
decltype(T{})  true          --- (2)   

  1. error C2514: 'IncompleteType' : class has no constructors

  2. error C2440: '<function-style-cast>' : cannot convert from 'initializer-list' to 'IncompleteType' Source or target has incomplete type


What compiler acts in accordance with standard? Note that simple string like std::cout << typeid(X(IncompleteType)).name() << std::endl; does not compile on all compilers for all variants of X (except for vc++ and X(T) == T).

解决方案

I believe that the behavior of Clang and MSVC are consistent with the standard in this situation. I think GCC is taking a bit of a short-cut here.

Let's put a few facts on the table first. The operand of a decltype expression is what is called an unevaluated operand, which are treated a bit differently due to fact that they are ultimately never evaluated.

Particularly, there are fewer requirements about the types being complete. Basically, if you have any temporary object (as parameters or return values in the functions or operators involved in the expression), they are not required to be complete (see Sections 5.2.2/11 and 7.1.6.2/5). But this only lifts the usual restriction of "you cannot declare an object of an incomplete type", but it does not lift the other restriction on incomplete types, which is that "you cannot call a member function of an incomplete type". And that's the kicker.

The expression decltype(T()) or decltype(T{}), where T is incomplete, must necessarily look-up the constructor(s) of the type T, as it's a (special) member function of that class. It's only the fact that it's a constructor call that creates a bit of an ambiguity (i.e., Is it just creating a temporary object? Or is it calling a constructor?). If it was any other member function, there would be no debate. Fortunately, the standard does settle that debate:

12.2/1

Even when the creation of the temporary object is unevaluated (Clause 5) or otherwise avoided (12.8), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: even if there is no call to the destructor or copy/move constructor, all the semantic restrictions, such as accessibility (Clause 11) and whether the function is deleted (8.4.3), shall be satisfied. However, in the special case of a function call used as the operand of a decltype-specifier (5.2.2), no temporary is introduced, so the foregoing does not apply to the prvalue of any such function call. - end note ]

The last sentence might be a bit confusing, but that only applies to the return-value of a function call. In other words, if you have T f(); function, and you declare decltype(f()), then T is not required to be complete or have any semantic checks on whether there is a constructor / destructor available and accessible for it.

In fact, this whole issue is exactly why there is a std::declval utility, because when you cannot use decltype(T()), you can just use decltype(std::declval<T>()), and declval is nothing more than a (fake) function that returns a prvalue of type T. But of course, declval is intended to be used in less trivial situations, such as decltype( f( std::declval<T>() ) ) where f would be a function taking an object of type T. And declval does not require that the type is complete (see Section 20.2.4). This is basically the way you get around this whole problem.

So, as far as GCC's behavior is concerned, I believe that it takes a short-cut as it attempts to figure out what the type of T() or T{} is. I think that as soon as GCC finds that T refers to a type name (not a function name), it deduces that this is a constructor call, and therefore, regardless of what the look-up finds as the actual constructor being called, the return type will be T (well, strictly speaking constructors don't have a return type, but you understand what I mean). The point here is that this could be a useful (faster) short-cut in an unevaluated expression. But this is not standard-compliant behavior, as far as I can tell.

And if GCC allows for CompleteType with the constructor either deleted or private, then that is also in direct contradiction with the above-quoted passage of the standard. The compiler is required to enforce all semantic restrictions in that situation, even if the expression is not evaluated.

Note that simple string like std::cout << typeid(X(IncompleteType)).name() << std::endl; does not compile on all compilers for all variants of X (except for vc++ and X(T) == T).

This is expected (except for MSVC and X(T) == T). The typeid and sizeof operators are similar to decltype in the sense that their operands are unevaluated, however, both of them have the additional requirement that the type of the resulting expression must be a complete type. It is conceivable that a compiler could resolve typeid for incomplete types (or at least, with partial type-info), but the standard requires a complete type such that compilers don't have to do this. I guess this is what MSVC is doing.

So, in this case, the T() and T{} cases fail for the same reason as for decltype (as I just explained), and the X(T) == T case fails because typeid requires a complete type (but MSVC manages to lift that requirement). And on GCC, it fails due to typeid requiring a complete type for all the X(T) cases (i.e., the short-cut GCC takes doesn't affect the outcome in the case of sizeof or typeid).

So, all in all, I think that Clang is the most standard-compliant of the three (not taking short-cuts or making extensions).

这篇关于模板代码中不完整的类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆