쿨보이 원 프로그래밍
21259 단어 templatesmetaprogrammingcpp
문제.
나의 프로젝트는 C++로 일부 세포 자동기를 설계하고 시뮬레이션하는 것이다.대략적으로 말하면 세포 자동기는 전역 규칙에 따라 자기 갱신을 하는 대상(명명 세포)이다.타임 스탬프마다 하나의 단원은 전역 규칙과 그 인접 단원에 따라 자신을 업데이트합니다.이런 세포는 통상적으로 세포의 기질을 나타낸다.간단하게 보기 위해 아래의 예에서 이 행렬은 a
std::array
에 불과하다.즉, 한 세포는 무엇이든지, 어떤 종류의 물체든지, 하나의 세포가 될 수 있다는 것이다.C++의 경우 일반적으로 사용templates
이 발생합니다.그러나'이것이 무엇이든 될 수 있다'보다 더 중요한 것은 이 일은 반드시 일반적인 행위를 실현해야 한다는 것이다.특히to_string
방법.모든 칸의 현재 상태를 파일에 쓰기 위해서 그것이 필요합니다.CellularAutomaton
과정의 시작은 다음과 같습니다.template<uint32_t rows, uint32_t cols>
class CellularAutomaton {
std::array<???, rows * cols> cells;
};
하지만???
유형은무엇일까.첫 번째 솔루션
모든 세포는 그 유형이 어떻든지 간에 일반적인 행위를 실현해야 한다.우리는 이곳에서 상속을 사용할 수 있다.모든 칸을 계승할
CellBase
클래스를 만듭니다.CellBase
는 순전히 하나의 인터페이스로 실례화할 수 없다. 왜냐하면 가상 방법만 있고 실현되지 않았기 때문이다.class CellBase {
public:
CellBase() = default;
virtual std::string to_string() const = 0;
};
이제 Automaton 클래스의 구현은 다음과 같습니다.template<uint32_t rows, uint32_t cols>
class CellularAutomaton {
std::array<std::unique_ptr<CellBase>, rows * cols> cells;
public:
std::unique_ptr<CellBase>& getCell(uint32_t row, uint32_t col) {
return cells[row * cols + col];
}
};
몇 가지 참고 사항:CellBase
는 실례화될 수 없기 때문이다.vtable
로부터 계승된 모든 종류는 하나CellBase
가 있을 것이다.std::unique_ptr<>&
로 돌아가야 한다.두 번째 솔루션
템플릿은 하나만 사용하면 됩니다.
template<typename T, uint32_t rows, uint32_t cols>
class CellularAutomatonV2 {
public:
T& getCell(uint32_t row, uint32_t col) {
return cells[row * cols + col];
}
std::array<T, rows * cols> cells;
};
현재 우리의 목표는 어떤 방식으로 단언T
하여 같은 서명을 가진 std::string to_string() const {}
방법을 실현하는 것이다.간단하게 하나
struct
를 정의하자. 실제로는 수신 유형과 서명trait
이다.이것은 정적 구성원 value
을 저장해서 유형 T
의 서명 실현 has_string
방법을 알려 줍니다.하나의 특질은 본질적으로 이렇다. 그것은 우리에게 유형적인 사실을 알려준다.아래의 코드는 이런 특질의 출발점이다.그것은 하나의 유형
S
과 하나의 함수 서명T
을 수신하고 후자도 하나의 유형이다.정적 구성원S
이 있습니다. 이 구성원은 "이 유형이 특정 서명을 사용하여 string을 호출하는 방법이 있습니까?"라는 질문에 대한 답을 저장합니다.value
에서 우리는 T
를 통해 이 방법을 포획하고 서명(이 명칭이 존재한다면)과 &T::method_name
을 비교할 수 있다.근데 어떻게 하지?template<typename T, typename S>
struct trait_has_to_string {
static bool constexpr value = ...;
};
스파나가 구원하러 가자.
SFINAE는 교체 실패가 오류가 아니라는 의미입니다.실제로 컴파일러가 템플릿 함수를 보았을 때, 이 함수에 전달되는 유형에 따라 코드를 생성하려고 시도합니다.예를 들면 다음과 같습니다.
template<typename T, typename U>
auto add(const T& a, const U& b) -> decltype(a + b) {
std::cout << "Calling general template" << std::endl;
return a + b;
}
template<typename U>
auto add(const std::string& a, const U& b) -> std::string {
std::cout << "Calling specialization const std::string&" << std::endl;
return a + std::to_string(b);
}
template<typename U>
auto add(const char* a, const U& b) -> std::string {
std::cout << "Calling specialization for const char*" << std::endl;
return std::string(a) + std::to_string(b);
}
S
호출할 때마다 컴파일러는 세 개의 템플릿 함수를 선택할 수 있습니다.다음 전화 번호: const std::string test = "testing";
std::cout << add(2, 2) << std::endl;
std::cout << add("testing ", 1) << std::endl;
std::cout << add(test, 1) << std::endl;
결과:Calling general template
4
Calling specialization for const char*
testing 1
Calling specialization const std::string&
testing 1
첫 번째 호출은 add
과 add(const std::string&, const U&)
서명이 있는 템플릿 함수를 사용할 수 없지만, 교체 실패는 오류가 아닙니다. 전달된 형식만 최소한 버전을 호출할 수 있습니다.첫 번째 호출에 대해 가장 일반적인 버전을 선택할 것입니다.두 번째 호출은 두 개의 템플릿 함수(add(const char*, const U&)
와 add(const T&, const U&)
에 적합하다.그것은 더욱 구체적이기 때문에 add(const std::string&, const U&)
를 선택할 것이다.컴파일러는 항상 가장 구체적인 일치를 시도할 것입니다.세 번째 전화는 예상대로 선택됩니다add(std::string&, const U&)
.그것만 있으면 우리는 클래스에 방법이 존재하는지 검사할 수 있다.그러나 계속하기 전에 첫 번째 템플릿 함수에
add(const char*, const U&)
서명이 있음을 주의하십시오.쿼리 표현식의 유형에 대한 키워드template<typename T, typename U>
auto add(const T& a, const U& b) -> decltype(a + b)
위의 성명에서, 이것은 함수가 표현식 decltype
과 같은 형식으로 되돌아오는 것을 의미할 뿐이다.문제로 돌아가다
다음 코드는 우리가 원하는 기교를 실현했다.
template<typename T, typename S>
struct trait_has_to_string {
using True = std::true_type;
using False = std::false_type;
template<typename U, U> struct Model;
template<typename U> static True Check(Model<S, &U::to_string> *);
template<typename U> static False Check(...);
static bool constexpr value = std::is_same<True, decltype(Check<T>(0))>();
};
우리는 이렇게 불러도 된다.trait_has_to_string<T, std::string(T::*)() const>::value;
기능 변경:template<typename T>
struct has_to_string {
static bool constexpr value = trait_has_to_string<T, std::string(T::*)() const>::value;
};
a+b
방법의 유형은 자신의 서명이다.정적 방법to_string
은 두 가지 버전이 있다.실례화True Check(Model<S, &U::to_string>
(즉 하나Model
와 서명이 완전히 일치하는 경우&U::to_string
만 있으면 첫 번째를 호출합니다.S
에 전달된 0은 하나Check<T>(0)
에 불과하다.첫 번째 버전이 실패하면 가장 일반적인 버전을 호출합니다.코드의 나머지 부분은 매우 간단하다.nullptr
와 std::true_type
는 각각 값std::true_type
과 true
의 유형이다.false
의 두 가지 버전을 작성할 수 있었는데 첫 번째 버전은 Check
이고 두 번째 버전은 true
이다.그러나 그들은 항상 같은 값을 되돌려준다.이러한 귀환 유형을 각각 false
과std::true_type
로 전문화하는 것은 의미가 있다.그리고 우리는 심지어 호출std::false_type
할 필요가 없다. 왜냐하면 실례화된 버전은 그 반환 값을 방법의 반환 형식으로 하기 때문이다.class CellularAutomaton {
static_assert(has_to_string<T>::value, "T does not implement std::string to_string() const;");
public:
T& getCell(uint32_t row, uint32_t col) {
return cells[row * cols + col];
}
std::array<T, rows * cols> cells;
};
그리고 두 가지 다른 세포 유형:struct CellTypeOne {
std::string to_string();
};
struct CellTypeTwo {
std::string to_string() const;
};
마지막으로 주요 문제는 다음과 같습니다.CellularAutomaton<CellTypeOne,2, 2> automaton1;
CellularAutomaton<CellTypeTwo,2, 2> automaton2;
현재 Check
는 static_assert
로 인해 실패할 것이다. automaton1
수식부호가 방법 서명에 없기 때문이다.이 기능은 이 방법이 없는 형식 const
을 호출하기 전에 오류를 검출하고 내보낼 수 있도록 합니다.이 외에도 이 방법이 실행되지 않은 형식에 대한 기본 반환을 지정할 수 있습니다.
Reference
이 문제에 관하여(쿨보이 원 프로그래밍), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/adrianotosetto/cool-boys-do-metaprogramming-45i7텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)