Programing

템플릿 인수 대체 순서가 중요한 이유는 무엇입니까?

crosscheck 2020. 12. 31. 22:49
반응형

템플릿 인수 대체 순서가 중요한 이유는 무엇입니까?


C ++ 11

14.8.2- 템플릿 인수 추론 -[temp.deduct]

7 함수 유형 및 템플릿 매개 변수 선언에 사용되는 모든 유형 및 표현식에서 대체가 발생합니다. 표현식에는 배열 경계 또는 유형이 아닌 템플릿 인수로 나타나는 것과 같은 상수 표현식뿐만 아니라 sizeof, 내부의 일반 표현식 (즉 , 상수가 아닌 표현식) decltype및 비 상수 표현식을 허용하는 기타 컨텍스트도 포함됩니다.


C ++ 14

14.8.2- 템플릿 인수 추론 -[temp.deduct]

7 함수 유형 및 템플릿 매개 변수 선언에 사용되는 모든 유형 및 표현식에서 대체가 발생합니다. 표현식에는 배열 경계 또는 유형이 아닌 템플릿 인수로 나타나는 것과 같은 상수 표현식뿐만 아니라 ,, 및 비 상수 표현식을 허용하는 기타 컨텍스트 내부의 일반 표현식 (즉 sizeof, 상수가 아닌 표현식)도 포함됩니다 decltype. 대체는 어휘 순서로 진행되며 추론이 실패하는 조건이 발생하면 중지됩니다 .



추가 된 문장은 C ++ 14에서 템플릿 매개 변수를 다룰 때 대체 순서를 명시 적으로 설명합니다.

대체 순서는 대부분의주의를 기울이지 않는 것입니다. 왜 이것이 중요한지에 대한 논문을 아직 찾지 못했습니다. 아마도 이것은 C ++ 1y가 아직 완전히 표준화되지 않았기 때문일 수 있지만, 나는 그러한 변경이 이유 때문에 도입되었을 것이라고 가정합니다.

질문:

  • 템플릿 인수 대체 순서가 왜 그리고 언제 중요합니까?

언급했듯이 C ++ 14는 템플릿 인수 대체 순서가 잘 정의되어 있다고 명시 적으로 말합니다. 보다 구체적으로 "어휘 순서 로 진행되고 대체로 인해 공제가 실패 할 때마다 중단됩니다.

C ++ 11에 비해 C ++ 14에서 다른 규칙에 따라 하나의 규칙으로 구성된 SFINAE 코드 를 작성하는 것이 훨씬 쉬울 것입니다. 또한 템플릿 대체의 정의되지 않은 순서로 인해 전체 애플리케이션이 어려움을 겪을 수있는 경우에서 벗어날 것입니다. 정의되지 않은 동작.

참고 : C ++ 14에 설명 된 동작은 C ++ 11에서도 항상 의도 된 동작이었으며, 그렇게 명시적인 방식으로 표현되지 않았다는 점에 유의해야합니다.



그러한 변화의 근거는 무엇입니까?

이 변경의 원래 이유 Daniel Krügler가 처음 제출 한 결함 보고서 에서 찾을 수 있습니다 .


추가 설명

SFINAE작성할 때 우리는 개발자로서 컴파일러에 의존하여 사용할 때 템플릿에서 유효하지 않은 유형 또는 표현식생성하는 대체를 찾습니다 . 이러한 유효하지 않은 엔티티가 발견되면 템플릿이 선언하는 모든 것을 무시하고 적절한 일치 항목을 찾기 위해 이동합니다.

대체 실패는 오류 가 아니라 단순한 .. "아, 작동하지 않았습니다 .. 계속 진행하십시오" .

문제는 잠재적 인 유효하지 않은 유형 및 표현식이 대체 즉각적인 컨텍스트 에서만 검색된다는 것 입니다.

14.8.2- 템플릿 인수 추론 -[temp.deduct]

8 대체로 인해 잘못된 유형 또는 표현식이 생성되면 유형 추론이 실패합니다. 유효하지 않은 유형 또는 표현식은 대체 인수를 사용하여 작성하면 형식이 잘못 될 수 있습니다.

[ 참고 : 액세스 확인은 대체 프로세스의 일부로 수행됩니다. -엔드 노트 ]

함수 유형 및 해당 템플릿 매개 변수 유형의 즉각적인 컨텍스트에서 유효하지 않은 유형 및 표현식 만 추론 실패를 초래할 수 있습니다.

[ 참고 : 대체 된 유형 및 표현식의 평가는 클래스 템플릿 전문화 및 / 또는 함수 템플릿 전문화의 인스턴스화, 암시 적으로 정의 된 함수 생성 등과 같은 부작용을 초래할 수 있습니다. 이러한 부작용은 "즉시 문맥 "과 같이 프로그램이 잘못 구성 될 수 있습니다. -엔드 노트 ]

즉, 즉각적이지 않은 컨텍스트 에서 발생하는 대체 는 여전히 프로그램을 잘못된 형식으로 렌더링하므로 템플릿 대체 순서가 중요합니다. 특정 템플릿의 전체 의미를 변경할 수 있습니다.

더 구체적으로는 주형 갖는 차이 일 수 있다 SFINAE에서 가능한 한 템플릿 있지 않습니다 .


실리 예

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)를 원하지만이 게시물에서 논의 된 표준 변경 이전에는 그러한 보장이 없었습니다.


대체의 즉각적인 컨텍스트는 다음을 foo(int)포함합니다.

  • (E)에 전달 된 것을 확인하고 T있다::type
  • (F)확인하십시오 만들고 inner_type<T>있다::type


경우 (F)에도 평가되는 (E)경우 잘못된 대체의 결과 또는 (F)이전에 평가되는 (E)우리의 짧은 (바보) 우리가 의도에도 불구하고 .. 예 SFINAE을 사용하지 않습니다 우리는 진단이 우리의 응용 프로그램이 잘못 형성되는 것을 말하는 얻을 것이다 foo(...)그런 경우에 사용됩니다.


참고 : 공지 사항 SomeType::type에없는 즉각적인 상황에 맞는 템플릿; typedef 내부에 오류가 발생 inner_type하면 응용 프로그램이 잘못 구성되고 템플릿이 SFINAE 를 사용하지 못합니다 .



이것이 C ++ 14의 코드 개발에 어떤 영향을 미칠까요?

이 변경은 어떤 컴파일러를 사용하든 어떤 방식 (및 순서)으로 평가되도록 보장되는 무언가를 구현하려는 언어 변호사 의 삶을 극적으로 완화 할 것입니다 .

또한 비 언어 변호사 에게 템플릿 인수 대체가보다 자연스러운 방식으로 작동하도록합니다 . 대체가 왼쪽 에서 오른쪽으로 발생하는 것은 erhm-like-any-way-the-compiler-wanna-do-it-like-erhm -... 보다 훨씬 직관적 입니다.


부정적인 의미가 없습니까?

내가 생각할 수있는 유일한 것은 대체 순서가 왼쪽 에서 오른쪽으로 발생하기 때문에 컴파일러가 비동기 구현을 사용하여 한 번에 여러 대체를 처리 할 수 ​​없다는 것입니다.

나는 아직 그러한 구현을 우연히 발견하지 못했고, 그것이 어떤 중요한 성능 향상을 가져올지는 의심 스럽지만 적어도 (이론적으로) 생각은 "부정적인"측면에 부합한다.

예를 들어 컴파일러는 필요한 경우 특정 지점 이후에 발생한 대체처럼 작동하는 메커니즘없이 특정 템플릿을 인스턴스화 할 때 동시에 대체를 수행하는 두 개의 스레드를 사용할 수 없습니다.



이야기

참고 :이 섹션에서는 템플릿 인수 대체 순서가 중요한시기와 이유를 설명하기 위해 실제 생활에서 가져올 수있는 예가 제공됩니다. 명확하지 않거나 잘못된 것이 있으면 알려주세요 (코멘트 섹션 사용).

열거자를 사용 하고 있으며 지정된 열거기본 을 쉽게 얻을 수있는 방법을 원한다고 상상해보십시오 .

기본적으로 우리는 (A)이상적으로 더 가까운 것을 원할 때 항상 글을 써야하는 것에 지쳤 습니다 (B).

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

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

원래 구현

말하고, 우리 underlying_value는 아래와 같이 구현을 작성하기로 결정했습니다 .

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

이것은 우리의 고통을 덜어 줄 것이며 우리가 원하는 것을 정확히하는 것 같습니다. 열거자를 전달하고 기본 값을 다시 가져옵니다.

우리는이 구현이 굉장하다고 스스로에게 말하고 우리의 동료 ( Don Quixote )에게 자리에 앉아 우리의 구현을 검토 한 후 프로덕션에 적용하도록 요청합니다.


코드 검토

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.

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 - Other Transformations - [meta.trans.other]

template<class T> struct underlying_type;

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

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.


THE KNIGHT IN SHINING ARMOUR

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.

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); }

THE WINDMILL

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).

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. The compiler is free to evaluate (D) before (C) since the substitution order isn't well-defined, and;

  2. 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.


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 might not be fighting windmills on this one, but he surely missed a very important dragon in the C++11 standard.

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);  
}

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.

ReferenceURL : https://stackoverflow.com/questions/22368022/why-does-the-order-of-template-argument-substitution-matter

반응형