为什么模板参数替换的顺序很重要? [英] Why does the order of template argument substitution matter?

查看:105
本文介绍了为什么模板参数替换的顺序很重要?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++ 11

14.8.2-模板参数推导-[temp.deduct]

14.8.2 - Template Argument Deduction - [temp.deduct]

7 替换发生在函数类型和模板参数声明中使用的所有类型和表达式中.表达式不仅包括常量表达式(例如出现在数组边界中或作为非类型模板参数的常量表达式),而且还包括sizeofdecltype内的通用表达式(即非常量表达式)以及其他允许非常量表达式的上下文

7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (ie. non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions.


C ++ 14

14.8.2-模板参数推导-[temp.deduct]

14.8.2 - Template Argument Deduction - [temp.deduct]

7 替换发生在函数类型和模板参数声明中使用的所有类型和表达式中.表达式不仅包括常量表达式(例如出现在数组边界中或作为非类型模板参数的常量表达式),而且还包括sizeofdecltype内的通用表达式(即非常量表达式)以及其他允许非常量表达式的上下文. 替换以词法顺序进行,并在遇到导致推论失败的条件时停止.

7 The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (ie. non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered.



添加的句子明确指出了在C ++ 14中处理模板参数时的替换顺序.



The added sentence explicitly states the order of substitution when dealing with template parameters in C++14.

替换顺序通常很少引起注意.我还没有找到关于这为何重要的论文.也许是因为C ++ 1y尚未完全标准化,但是我认为必须引入这种更改是有原因的.

The order of substitution is something that most often isn't given a lot of attention. I have yet to find a single paper on why this matters. Maybe this is because C++1y hasn't been fully standardized yet, but I'm assuming such a change must have been introduced for a reason.

问题:

  • 为什么以及何时使用模板参数替换顺序很重要?

推荐答案

如前所述,C ++ 14明确指出模板参数替换的顺序是明确定义的;更具体地说,将确保按词汇顺序进行,并在替换导致推论失败时暂停.

As stated C++14 explicitly says that the order of template argument substitution is well-defined; more specifically it will be guaranteed to proceed in "lexical order and halt whenever a substitution causes the deduction to fail.

与C ++ 11相比,编写 SFINAE -代码由C ++ 14中的一个规则(取决于另一个规则)组成要容易得多,我们还将摆脱未定义顺序的情况模板替换会使我们的整个应用程序遭受不确定的行为.

Compared to C++11 it will be much easier to write SFINAE-code that consists of one rule depending on another in C++14, we will also move away from cases where undefined ordering of template substitution can make our entire application suffer from undefined-behaviour.

注意:必须注意,C ++ 14中描述的行为一直是预期的行为,即使在C ++ 11中也是如此,只是没有措辞

Note: It's important to note that the behavior described in C++14 has always been the intended behavior, even in C++11, just that it hasn't been worded in such an explicit way.

此更改的最初原因可以在由 DanielKrügler提交的缺陷报告中找到:

The original reason behind this change can be found in a defect report originally submitted by Daniel Krügler:

  • C ++标准核心语言缺陷报告和可接受的问题,修订版88
    • C++ Standard Core Language Defect Reports and Accepted Issues, Revision 88
      • 1227. Mixing immediate and non-immediate contexts in deduction failure

      进一步说明

      当编写 SFINAE 时,作为开发人员,我们依赖编译器在模板中查找会在模板中产生无效的 type expression 的任何替换.用过的.如果找到了这样的无效实体,我们将不理会模板所声明的内容,而是继续寻找合适的匹配项.

      When writing SFINAE we as developers depend on the compiler to find any substitution that would yield an invalid type or expression in our template when used. If such invalid entity is found we'd like to disregard whatever the template is declaring and move on to hopefully find a suitable match.

      替换失败不是错误,而仅仅是.."aw,这没用.请继续前进" .

      问题在于,潜在的无效类型和表达式仅在替换的立即上下文中查找.

      The problem is that potential invalid types and expressions are only looked for in the immediate context of the substitution.

      14.8.2-模板参数推导-[temp.deduct]

      8 如果替换导致无效的类型或表达式,则类型推导将失败.无效的类型或表达式是如果使用替换的参数编写的格式或表达式,则会格式错误.

      8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments.

      [注意:访问检查是替换过程的一部分. -尾注]

      [ Note: Access checking is done as part of the substitution process. --end note ]

      只有在函数类型及其模板参数类型的直接上下文中无效的类型和表达式才可能导致推论失败.

      [注意::对替换类型和表达式的求值可能会导致副作用,例如实例化类模板专业化和/或函数模板专业化,隐式定义的生成这样的副作用不在即时上下文"中,并可能导致程序格式错误. -尾注]

      [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the "immediate context" and can result in the program being ill-formed. --end note]

      换句话说,在非立即上下文中发生的替换仍然会使程序格式错误,这就是为什么模板替换的顺序很重要的原因;它可以改变某个模板的整体含义.

      In other words a substitution that occurs in a non-immediate context will still render the program ill-formed, which is why the order of template substitutions is important; it can change the whole meaning of a certain template.

      更具体地说,这可能是拥有在SFINAE中可用的 模板和没有 的模板之间的区别.

      More specifically it can be the difference between having a template which is usable in SFINAE, and a template which isn't.

      愚蠢的例子

      template<typename SomeType>
      struct inner_type { typedef typename SomeType::type type; };
      

      template<
        class T,
        class   = typename T::type,            // (E)
        class U = typename inner_type<T>::type // (F)
      > void foo (int);                        // preferred
      

      template<class> void foo (...);          // fallback
      

      struct A {                 };  
      struct B { using type = A; };
      
      int main () {
        foo<A> (0); // (G), should call "fallback "
        foo<B> (0); // (H), should call "preferred"
      }
      

      在标记为(G)的行上,我们希望编译器首先检查(E),如果该检查成功,则对(F)进行评估,但是在本文讨论的标准更改之前,没有这样的保证.

      On the line marked (G) we want the compiler to first check (E) and if that succeeds evaluate (F), but before the standard change discussed in this post there was no such guarantee.


      foo(int)中替换的直接上下文包括;

      The immediate context of the substitutions in foo(int) includes;

      • (E)确保传入的T具有::type
      • (F)确保inner_type<T>具有::type
      • (E) making sure that the passed in T has ::type
      • (F) making sure that inner_type<T> has ::type


      如果即使(E)导致无效替换也对(F)进行了评估,或者如果(F)(E)之前进行了评估,我们的简短示例(愚蠢的示例)将不会使用SFINAE,我们将获得诊断信息表示我们的应用程序格式错误..即使我们打算在这种情况下使用foo(...).

      If (F) is evaluated even though (E) results in an invalid substitution, or if (F) is evaluated before (E) our short (silly) example won't make use of SFINAE and we will get an diagnostic saying that our application is ill-formed.. even though we intended for foo(...) to be used in such case.


      注意:请注意,SomeType::type不在模板的立即上下文中; inner_type内的 typedef 失败会导致应用程序格式错误,并阻止模板使用 SFINAE .

      Note: Notice that SomeType::type is not in the immediate context of the template; a failure in the typedef inside inner_type will render the application ill-formed and prevent the template from making use of SFINAE.

      此更改将极大地简化语言律师的工作,无论他们使用的是哪种编译器,尝试实施可以保证以某种方式(和顺序)进行评估的内容.

      The change will dramatically ease the life of language-lawyers trying to implement something which is guaranteed to be evaluated in a certain way (and order), no matter what conforming compiler they are using.

      这也将使模板参数替换对 non-language-lawyers 而言更自然.从从左到右发生替换比像erhm那样的任何方式的编译器想要像erhm一样直观... .

      It will also make template argument substitution behave in a more natural way to non-language-lawyers; having the substitution occur from left-to-right is far more intuitive than erhm-like-any-way-the-compiler-wanna-do-it-like-erhm-....

      没有任何负面影响吗?

      我唯一想到的是,由于替换顺序是从从左至右发生的,因此不允许编译器使用异步实现一次处理多个替换.

      The only thing I can think of is that since the order of substitution will occur from left-to-right a compiler is not permitted to handle multiple substitutions at once using an asynchronous implementation.

      我还没有偶然发现这种实现方式,并且我怀疑这样做是否会带来任何重大的性能提升,但是至少(从理论上)这种想法有点适合事物的消极"方面.

      I have yet to stumble across such implementation, and I doubt that it would result in any major performance gain, but at least the thought (in theory) kinda fits on the "negative" side of things.

      作为一个例子:如果需要,编译器将无法使用两个线程在实例化某个模板时同时进行替换,而没有任何机制像在某个特定点永远不会发生之后发生的替换那样工作.

      As an example: A compiler will not be able to use two threads that simultaneously does substitutions when instantating a certain template without any mechanism to act like the substitutions that occured after a certain point never happened, if that is required.

      注意:本节将提供一个可能来自现实生活的示例,以描述模板参数替换顺序何时以及为何如此重要.如果有任何不清楚的地方,甚至可能是错误的,请让我知道(使用评论部分).

      Note: An example that could have been taken from real life will be presented in this section to describe when and why the order of template argument substitution matters. Please let me know (using the comment section) if anything is not clear enough, or maybe even wrong.

      想象一下我们正在与枚举器一起工作,并且我们希望有一种方法可以轻松地获得指定 底层 > 枚举 .

      Imagine that we are working with enumerators and that we'd like a way to easily obtain the underlying value of the specified enumeration.

      基本上,当我们理想地想要更接近(B)的东西时,总是厌烦总是不得不写(A)的事情.

      Basically we are sick and tired of always having to write (A), when we would ideally want something closer to (B).

      auto value = static_cast<std::underlying_type<EnumType>::type> (SOME_ENUM_VALUE); // (A)
      

      auto value = underlying_value (SOME_ENUM_VALUE);                                  // (B)
      


      原始实施

      说完了,我们决定编写一个underlying_value的实现,如下所示.

      Said and done, we decide to write an implementation of underlying_value looking as the below.

      template<class T, class U = typename std::underlying_type<T>::type> 
      U underlying_value (T enum_value) { return static_cast<U> (enum_value); }
      

      这将减轻我们的痛苦,并且看起来完全可以实现我们想要的;我们传入一个枚举数,然后取回基础值.

      This will ease our pain, and seems to do exactly what we want; we pass in an enumerator, and get the underlying value back.

      我们告诉自己,该实现非常棒,请我们的同事( Don Quixote )坐下来审查我们的实现,然后再将其投入生产.

      We tell ourselves that this implementation is awesome and ask a colleague of ours (Don Quixote) to sit down and review our implementation before pushing it out into production.

      代码审查

      Don Quixote 是一位经验丰富的C ++开发人员,一只手捧着一杯咖啡,另一只手捧着C ++标准.他如何双手忙于编写一行代码,这是一个谜,但这是另一回事.

      Don Quixote is an experienced C++ developer that has a cup of coffee in one hand, and the C++ standard in the other. It's a mystery how he manages to write a single line of code with both hands busy, but that's a different story.

      他查看了我们的代码并得出结论,该实现是不安全的,我们需要防止std::underlying_type出现不确定的行为,因为我们可以传入不是枚举类型的T .

      He reviews our code and comes to the conclusion that the implementation is unsafe, we need to guard std::underlying_type from undefined-behaviour since we can pass in a T which is not of enumeration type.

      20.10.7.6-其他转换-[meta.trans.other]

      template<class T> struct underlying_type;
      

      条件: T应为枚举类型(7.2)
      注释:成员typedef type应命名为T的基础类型.

      Condition: T shall be an enumeration type (7.2)
      Comments: The member typedef type shall name the underlying type of T.

      注意:该标准为underlying_type指定了一个条件,但它并没有进一步说明如果使用a实例化它将发生什么. 非枚举.由于我们不知道在这种情况下会发生什么,因此用法属于 undefined-behavior ;它可以是纯 UB ,使应用程序格式不正确,也可以在线订购可食用的内衣.

      Note: The standard specifies a condition for underlying_type, but it doesn't go any further to specifiy what will happen if it's instantiated with a non-enum. Since we don't know what will happen in such case the usage falls under undefined-behavior; it could be pure UB, make the application ill-formed, or order edible underwear online.

      护甲的骑士

      Don对我们始终应遵守C ++标准大喊大叫,对于所做的事情,我们应该感到极大的耻辱.这是不可接受的.

      Don yells something about how we always should honor the C++ standard, and that we should feel tremendous shame for what we have done.. it's unacceptable.

      当他冷静下来并喝了几口咖啡后,他建议我们更改实现以添加保护,以禁止使用不允许的实例化std::underlying_type.

      After he has calmed down, and had a few more sips of coffee, he suggests that we change the implementation to add protection against instantiating std::underlying_type with something which isn't allowed.

      template<
        typename T,
        typename   = typename std::enable_if<std::is_enum<T>::value>::type,  // (C)
        typename U = typename std::underlying_type<T>::type                  // (D)
      >
      U underlying_value (T value) { return static_cast<U> (value); }
      


      风神

      我们感谢Don的发现,现在对我们的实现感到满意,但是直到我们意识到C ++ 11中模板参数替换的顺序没有很好地定义(也没有说明何时停止替换) ).

      We thank Don for his discoveries and are now satisfied with our implementation, but only until we realize that the order of template argument substitution isn't well-defined in C++11 (nor is it stated when the substitution will stop).

      作为C ++ 11编译,我们的实现仍然可以使用T而不是 enumeration 类型的T实例化std::underlying_type,这有两个原因:

      Compiled as C++11 our implementation can still cause an instantiation of std::underlying_type with a T that isn't of enumeration type because of two reasons:

      1. 由于替换顺序定义不明确,编译器可以在(C)之前先评估(D);并且

      即使编译器在(D)之前评估(C),也不能保证不会评估(D),C ++ 11没有明确说明替换链何时必须停止的子句.

      even if the compiler evaluates (C) before (D), it's not guaranteed that it won't evaluate (D), C++11 doesn't have a clause explicitly saying when the substitution chain must stop.


      Don的实现将摆脱C ++ 14中的 undefined-behavior ,但仅因为C ++ 14明确声明替换将以词法顺序进行 >,并且只要替换导致推论失败,它就会暂停.


      The implementation by Don will be free from undefined-behavior in C++14, but only because C++14 explicitly states that the substitution will proceed in lexical order, and that it will halt whenever a substitution causes deduction to fail.

      Don可能并没有在这台飞机上与风车作战,但他肯定错过了C ++ 11标准中一条非常重要的龙.

      Don might not be fighting windmills on this one, but he surely missed a very important dragon in the C++11 standard.

      在C ++ 11中有效的实现方式需要确保,无论模板参数替换发生的顺序如何,std::underlying_type的实例化都不会具有无效的类型.

      A valid implementation in C++11 would need to make sure that no matter the order in which the substitution of template parameters occur the instantation of std::underlying_type won't be with an invalid type.

      #include <type_traits>
      
      namespace impl {
        template<bool B, typename T>
        struct underlying_type { };
      
        template<typename T>
        struct underlying_type<true, T>
          : std::underlying_type<T>
        { };
      }
      
      template<typename T>
      struct underlying_type_if_enum
        : impl::underlying_type<std::is_enum<T>::value, T>
      { };
      
      template<typename T, typename U = typename underlying_type_if_enum<T>::type>
      U get_underlying_value (T value) {
        return static_cast<U> (value);  
      }
      


      注意: underlying_type之所以被使用,是因为它是使用标准中的内容与标准中的内容进行比较的一种简单方法.重要的一点是,使用非枚举实例化它是未定义的行为.


      Note: underlying_type was used because it's a simple way to use something in the standard against what is in the standard; the important bit is that instantiating it with a non-enum is undefined behavior.

      之前在这篇文章中链接的缺陷报告使用了一个更为复杂的示例,该示例假设了有关此问题的广泛知识.我希望这个故事对于那些不太了解该主题的人来说是一个更合适的解释.

      The defect-report previously linked in this post uses a much more complex example which assumes extensive knowledge about the matter. I hope this story is a more suitable explanation for those who are not well read up on the subject.

      这篇关于为什么模板参数替换的顺序很重要?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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