이 시점 에서이 라이브러리는 실험적이며 순수한 호기심입니다. 인터페이스의 안정성이나 구현 품질은 보장되지 않습니다. 자신의 위험에 사용하십시오.
Dyno는 바닐라 C ++보다 런타임 다형성 문제를 더 잘 해결합니다. 이는 비에 맞지 않을 수있는 인터페이스를 정의 할 수있는 방법을 제공하며 다형성 객체를 저장하고 가상 메소드로 파견하는 완전히 사용자 정의 가능한 방법을 제공합니다. 상속, 힙 할당 또는 편안한 가치의 의미를 남기는 것은 필요하지 않으며 바닐라 C ++를 능가하면서 그렇게 할 수 있습니다.
Dyno는 Rust Trait Objects, GO 인터페이스, Haskell 유형 클래스 및 가상 개념의 순수한 라이브러리 구현입니다. 후드 아래에서, 그것은 유형 삭제라고하는 C ++ 기술을 사용하는데, 이는 std::any , std::function 및 기타 많은 유용한 유형의 아이디어입니다.
# include < dyno.hpp >
# include < iostream >
using namespace dyno ::literals ;
// Define the interface of something that can be drawn
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::method< void (std::ostream&) const >
)) { };
// Define how concrete types can fulfill that interface
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = [](T const & self, std::ostream& out) { self. draw (out); }
);
// Define an object that can hold anything that can be drawn.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable> poly_;
};
struct Square {
void draw (std::ostream& out) const { out << " Square " ; }
};
struct Circle {
void draw (std::ostream& out) const { out << " Circle " ; }
};
void f (drawable const & d) {
d. draw (std::cout);
}
int main () {
f (Square{}); // prints Square
f (Circle{}); // prints Circle
}또는 이것이 너무 많은 보일러 플레이트라고 생각하고 매크로를 사용하여 서있을 수 있다면 다음은 동일합니다.
# include < dyno.hpp >
# include < iostream >
// Define the interface of something that can be drawn
DYNO_INTERFACE (Drawable,
(draw, void (std::ostream&) const )
);
struct Square {
void draw (std::ostream& out) const { out << " Square " ; }
};
struct Circle {
void draw (std::ostream& out) const { out << " Circle " ; }
};
void f (Drawable const & d) {
d. draw (std::cout);
}
int main () {
f (Square{}); // prints Square
f (Circle{}); // prints Circle
}이것은 C ++ 17 라이브러리입니다. 구형 컴파일러를 지원하기위한 노력은 없습니다 (죄송합니다). 라이브러리는 다음 컴파일러와 함께 작동하는 것으로 알려져 있습니다.
| 컴파일러 | 버전 |
|---|---|
| GCC | > = 7 |
| 그 소리 | > = 4.0 |
| 애플 클랑 | > = 9.1 |
라이브러리는 boost.hana 및 boost.callabletraits에 따라 다릅니다. 단위 테스트는 libawful에 따라 달라지며 벤치 마크는 Google Benchmark, Boost.typeerasure 및 MPARK.Variant에 따라 다르지만 라이브러리를 사용할 필요는 없습니다. 로컬 개발의 경우 dependencies/install.sh 스크립트를 사용하여 모든 종속성을 자동으로 설치할 수 있습니다.
Dyno 는 헤더 전용 라이브러리이므로 자격을 구축 할 것이 없습니다. Compiler의 헤더 검색 경로에 include/ 디렉토리를 추가하고 (종속성이 충족되었는지 확인하십시오). 그러나 구축 할 수있는 단위 테스트, 예제 및 벤치 마크가 있습니다.
(cd dependencies && ./install.sh) # Install dependencies; will print a path to add to CMAKE_PREFIX_PATH
mkdir build
(cd build && cmake .. -DCMAKE_PREFIX_PATH= " ${PWD} /../dependencies/install " ) # Setup the build directory
cmake --build build --target examples # Build and run the examples
cmake --build build --target tests # Build and run the unit tests
cmake --build build --target check # Does both examples and tests
cmake --build build --target benchmarks # Build and run the benchmarks 프로그래밍에서는 공통 인터페이스이지만 동적 유형이 다른 객체를 조작해야 할 필요성이 매우 자주 발생합니다. C ++는이를 상속으로 해결합니다.
struct Drawable {
virtual void draw (std::ostream& out) const = 0;
};
struct Square : Drawable {
virtual void draw (std::ostream& out) const override final { ... }
};
struct Circle : Drawable {
virtual void draw (std::ostream& out) const override final { ... }
};
void f (Drawable const * drawable) {
drawable-> draw (std::cout);
}그러나이 접근법에는 몇 가지 단점이 있습니다. 그것은
방해
Square 과 Circle Drawable 인터페이스를 충족시키기 위해서는 둘 다 Drawable 기본 클래스에서 상속해야합니다. 이를 위해서는 해당 클래스를 수정할 수있는 라이센스가 있어야하므로 상속을 매우 확장 할 수 없습니다. 예를 들어, std::vector<int> 어떻게 Drawable 인터페이스를 충족시킬 수 있습니까? 당신은 단순히 할 수 없습니다.
가치 의미와 호환되지 않습니다
상속은 당신이 다형성 포인터 나 객체 자체 대신 객체에 대한 참조를 통과해야하며, 이는 나머지 언어 및 표준 라이브러리와 매우 나쁜 역할을합니다. 예를 들어, Drawable S의 벡터를 어떻게 복사 하시겠습니까? 가상 clone() 메소드를 제공해야하지만 이제 인터페이스를 엉망으로 만들었습니다.
동적 스토리지와 밀접하게 결합됩니다
가치 의미가 부족하기 때문에, 우리는 일반적으로 힙에 이러한 다형성 물체를 할당하게됩니다. 이것은 우리가 동적 스토리지 지속 시간이 전혀 필요하지 않았기 때문에 끔찍하게 비효율적이고 의미 적으로 잘못된 것입니다. 자동 저장 시간 (예 : 스택에)이있는 객체 만 충분했기 때문입니다.
인라인을 방지합니다
시간의 95%, 우리는 다형성 포인터 또는 기준을 통해 가상 방법을 호출하게됩니다. 이를 위해서는 세 가지 간접 사항이 필요합니다. 하나는 객체 내부의 vtable에 포인터를로드하고, 하나는 vtable의 오른쪽 항목을로드하고, 하나는 기능 포인터로 간접 호출을위한 것입니다. 이 모든 점프는 컴파일러가 좋은 인라인 결정을 내리기가 어렵습니다. 그러나 간접 호출을 제외한 이러한 모든 간접 사항을 피할 수 있습니다.
불행히도, 이것은 C ++가 우리를 위해 만든 선택이며, 이것이 동적 다형성이 필요할 때 우리가 구속되는 규칙입니다. 아니면 정말입니까?
Dyno는 위에 나열된 단점없이 C ++의 런타임 다형성 문제를 해결합니다. 그것은:
무관심
해당 유형에 대한 수정이 필요하지 않고 유형에 의해 인터페이스를 충족시킬 수 있습니다. 도대체, 유형은 다른 방식으로 동일한 인터페이스를 충족시킬 수도 있습니다! Dyno 와 함께, 당신은 말도 안되는 계층의 작별 인사를 할 수 있습니다.
가치 의미를 기반으로 100%
다형성 대상은 자연 가치 의미와 함께 그대로 전달 될 수 있습니다. 다형성 대상을 복사해야합니까? 물론, 사본 생성자가 있는지 확인하십시오. 그들이 복사하지 않도록하고 싶습니까? 물론 삭제 된대로 표시하십시오. Dyno를 사용하면 바보 같은 clone() 방법과 API에서 포인터의 확산이 과거의 것들입니다.
특정 스토리지 전략과 결합되지 않습니다
다형성 물체가 저장되는 방식은 실제로 구현 세부 사항이며 해당 객체를 사용하는 방식을 방해해서는 안됩니다. Dyno는 객체가 저장되는 방식을 완전히 제어 할 수 있습니다. 작은 다형성 대상이 많습니까? 물론, 로컬 버퍼에 보관하고 할당을 피하십시오. 아니면 힙에 물건을 보관하는 것이 합리적입니까? 물론, 계속하십시오.
최상의 가능한 성능을 달성하기위한 유연한 파견 메커니즘
VTABLE에 포인터를 저장하는 것은 동적 파견을 수행하기위한 여러 구현 전략 중 하나 일뿐입니다. Dyno는 Dynamic Dispatch가 어떻게 발생하는지 완전히 제어 할 수 있으며 실제로 VTables를 이길 수 있습니다. 핫 루프로 호출되는 함수가있는 경우 예를 들어 객체에 직접 저장하고 Vtable 간접을 건너 뛸 수 있습니다. 또한 응용 프로그램 별 지식을 사용할 수 있습니다. 컴파일러는 일부 동적 통화 (라이브러리 수준의 Devirtualization)를 최적화 할 수 없습니다.
먼저 일반 인터페이스를 정의하고 이름을 부여하는 것으로 시작합니다. Dyno는 간단한 도메인 별 언어를 제공합니다. 예를 들어, 그릴 수있는 유형을 설명하는 인터페이스 Drawable 인터페이스를 정의해 봅시다.
# include < dyno.hpp >
using namespace dyno ::literals ;
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::method< void (std::ostream&) const >
)) { }; 이것은 Drawable std::ostream 참조하는 draw 라는 메소드가있는 것에 대한 인터페이스를 나타내는 것으로 정의합니다. Dyno는 이러한 인터페이스를 Dynamic Concepts라고 부릅니다. 이들은 유형 (C ++ 개념)에 의해 충족되기위한 요구 사항 세트를 설명하기 때문입니다. 그러나 C ++ 개념과 달리 이러한 동적 개념은 런타임 인터페이스를 생성하는 데 사용되므로 이름은 동적입니다 . 위의 정의는 기본적으로 다음과 같습니다.
struct Drawable {
virtual void draw (std::ostream&) const = 0;
};인터페이스가 정의되면 다음 단계는 실제로이 인터페이스를 만족시키는 유형을 만드는 것입니다. 상속을 통해 다음과 같은 글을 쓸 것입니다.
struct Square : Drawable {
virtual void draw (std::ostream& out) const override final {
out << " square " << std::endl;
}
};Dyno를 사용하면 다형성이 무관심하며 대신 개념 맵 (C ++ 0X 개념 맵 이후)을 통해 제공됩니다.
struct Square { /* ... */ };
template <>
auto const dyno::concept_map<Drawable, Square> = dyno::make_concept_map(
" draw " _s = [](Square const & square, std::ostream& out) {
out << " square " << std::endl;
}
);이 구성은
dyno::네임 스페이스에 정의 된concept_map이라는 C ++ 14 변수 템플릿의 전문화입니다. 그런 다음dyno::make_concept_map(...)로 해당 전문화를 초기화합니다.
람다의 첫 번째 매개 변수는 위의 메소드로 draw 선언 할 때 암시 적 *this 매개 변수입니다. 비회원 기능을 지우는 것도 가능합니다 (관련 섹션 참조).
이 개념 맵은 유형 Square Drawable 개념을 충족시키는 방법을 정의합니다. 어떤 의미에서, 그것은 타입 Square 개념의 구현에 매핑하여 항소에 동기를 부여합니다. 유형이 개념의 요구 사항을 충족시킬 때, 우리는 유형이 그 개념의 모델 (또는 모델)이라고 말합니다. 이제 Square Drawable 개념의 모델이므로 정사각형 다형성으로 Square Drawable 것으로 사용하고 싶습니다. 전통적인 상속을 통해 우리는 다음과 같은 기본 클래스에 대한 포인터를 사용합니다.
void f (Drawable const * d) {
d-> draw (std::cout);
}
f ( new Square{}); 다이노를 사용하면 다형성과 가치 의미론이 호환되며 다형성 유형이 통과하는 방식은 고도로 사용자 정의 될 수 있습니다. 이렇게하려면 Drawable 수있는 모든 것을 보유 할 수있는 유형을 정의해야합니다. 우리는 다형성 기능을 오가는 것이 그 Drawable* 대신에 그 유형입니다. 이 래퍼를 정의하는 데 도움을주기 위해 Dyno는 dyno::poly 컨테이너를 제공하며, 이는 주어진 개념을 만족하는 임의의 객체를 보유 할 수 있습니다. 보시다시피, dyno::poly 이중 역할을합니다. 다형성 대상을 저장하고 방법의 동적 파견을 처리합니다. dyno::poly 에 얇은 래퍼를 작성하기 만하면 원하는 인터페이스를 정확히 제공하기 만하면됩니다.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable> poly_;
};참고 : 인터페이스에서 직접
dyno::poly기술적으로 사용할 수 있습니다. 그러나dyno::poly보다 실제 방법을 가진 래퍼를 사용하는 것이 훨씬 더 편리하므로 래퍼를 작성하는 것이 좋습니다.
이것을 분해합시다. 먼저, 우리는 Drawable 개념을 모델링하는 모든 것을 위해 다형성 컨테이너 인 멤버 poly_ 정의합니다.
dyno::poly<Drawable> poly_; 그런 다음 임의의 유형 T 에서이 컨테이너를 구성 할 수있는 생성자를 정의합니다.
template < typename T>
drawable (T x) : poly_{x} { } 여기서 말할 수없는 가정은 실제로 Drawable 개념을 모델링 T 것입니다. 실제로, Type T 의 객체에서 dyno::poly 만들 때 Dyno는 가서 Drawable 및 T 로 정의 된 개념 맵을 보게됩니다. 그러한 컨셉 맵이없는 경우, 라이브러리는 우리가 그것을 지원하지 않는 유형에서 dyno::poly 만들려고한다고보고하며 프로그램은 컴파일하지 않습니다.
마지막으로, 위의 정의의 가장 이상하고 가장 중요한 부분은 draw 방법의 부분입니다.
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); } 여기서 발생하는 것은 .draw 우리의 drawable 객체에서 호출되면 실제로 dyno::poly 에 저장된 객체의 "draw" 기능의 구현에 실제로 동적 디스패치를 수행하여 호출하는 것입니다. 이제, Drawable 모든 것을 받아들이는 기능을 만들려면 더 이상 인터페이스의 포인터와 소유권에 대해 걱정할 필요가 없습니다.
void f (drawable d) {
d. draw (std::cout);
}
f (Square{});그건 그렇고, 이것이 바보라고 생각한다면 템플릿을 사용해야한다고 생각한다면, 당신은 맞습니다. 그러나 런타임 다형성이 필요한 다음을 고려하십시오.
drawable get_drawable () {
if ( some_user_input ())
return Square{};
else
return Circle{};
}
f (get_drawable()); 엄밀히 말하면, dyno::poly 랩핑 할 필요는 없지만, 그렇게하면 Dyno 와 나머지 코드 사이에 멋진 장벽이있어 다형 층이 어떻게 구현되는지 걱정할 필요가 없습니다. 또한, 우리는 위의 정의에서 dyno::poly 어떻게 구현되었는지를 크게 무시했습니다. 그러나 dyno::poly 성능의 요구에 맞게 사용자 정의 할 수있는 다형성 객체를위한 매우 강력한 정책 기반 컨테이너입니다. drawable 래퍼를 만들면 Code의 나머지 부분에 영향을 미치지 않고 성능을 위해 dyno::poly 가 사용하는 구현 전략을 쉽게 조정할 수 있습니다.
dyno::poly 에서 사용자 정의 할 수있는 첫 번째 측면은 물체가 컨테이너 내부에 저장되는 방식입니다. 기본적으로, 우리는 상속 기반 다형성과 마찬가지로 실제 물체에 대한 포인터를 저장합니다. 그러나 이것은 종종 가장 효율적인 구현이 아니기 때문에 dyno::poly 사용자 정의 할 수있는 이유입니다. 그렇게하려면 스토리지 정책을 dyno::poly 에 전달하십시오. 예를 들어, drawable 래퍼를 정의하여 로컬 버퍼에 최대 16 바이트를 저장하려고하지만 물체가 더 크면 힙으로 떨어집니다.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable, dyno::sbo_storage< 16 >> poly_;
// ^^^^^^^^^^^^^^^^^^^^^ storage policy
}; 정책을 제외하고는 우리의 정의에서 변경된 것이 없습니다. 그것은 Dyno 의 매우 중요한 교리 중 하나입니다. 이러한 정책은 구현 세부 사항이며 코드 작성 방식을 변경해서는 안됩니다. 위의 정의를 사용하면 이제 이전과 마찬가지로 drawable S를 생성 할 수 있으며 16 바이트로 드로 drawable 객체를 생성 할 때 할당이 발생하지 않습니다. 그러나 맞지 않으면 dyno::poly 힙에 큰 버퍼를 할당합니다.
실제로 할당을하고 싶지 않다고 가정 해 봅시다. 문제 없습니다. 정책을 dyno::local_storage<16> 로 변경하십시오. 로컬 스토리지에 너무 큰 객체에서 drawable 객체를 구성하려고하면 프로그램이 컴파일되지 않습니다. 우리는 할당을 저장하고있을뿐만 아니라 전통적인 상속 기반 접근법과 비교할 때 다형성 대상에 액세스 할 때마다 포인터 간접을 저장하고 있습니다. 특정 사용 사례에 대한 이러한 (중요한) 구현 세부 사항을 조정하면 클래식 상속보다 프로그램을 훨씬 더 효율적으로 만들 수 있습니다.
dyno::remote_storage 및 dyno::non_owning_storage 와 같은 다른 스토리지 정책도 제공됩니다. dyno::remote_storage 기본값으로, 항상 힙 할당 객체에 대한 포인터를 저장합니다. dyno::non_owning_storage 해당 물체의 수명에 대해 걱정하지 않고 이미 존재하는 객체에 대한 포인터를 저장합니다. 이를 통해 객체보다 소유하지 않은 다형성 뷰를 구현할 수 있습니다. 이는 매우 유용합니다.
맞춤형 스토리지 정책은 매우 쉽게 만들 수 있습니다. 자세한 내용은 <dyno/storage.hpp> 참조하십시오.
우리가 dyno::poly 소개했을 때, 우리는 그것이 두 가지 역할을한다고 언급했습니다. 첫 번째는 다형성 물체를 저장하는 것이고, 두 번째는 동적 디스패치를 수행하는 것입니다. 스토리지를 사용자 정의 할 수있는 것처럼 정책을 사용하여 동적 파견을 수행하는 방식도 사용자 정의 할 수 있습니다. 예를 들어, vtable에 대한 포인터를 저장하는 대신 vtable을 drawable 물체 자체에 저장하도록 drawable 래퍼를 정의해 봅시다. 이렇게하면 가상 함수에 액세스 할 때마다 간접적으로 하나도 방향을 피할 수 있습니다.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
using Storage = dyno::sbo_storage< 16 >; // storage policy
using VTable = dyno::vtable<dyno::local<dyno::everything>>; // vtable policy
dyno::poly<Drawable, Storage, VTable> poly_;
}; Vtable 정책 외에는 우리의 drawable 유형의 정의에서 변경해야 할 것이 없습니다. 또한 원하는 경우 VTABLE 정책과 독립적으로 저장 정책을 변경할 수 있습니다. 위의 점에서, 우리는 모든 간접을 저장하더라도, 우리는 vtable을 로컬로 유지해야하기 때문에 drawable 물체를 더 크게 만들어서 비용을 지불하고 있습니다. VTable에 많은 기능이 있으면 이것은 금지 될 수 있습니다. 대신, 대부분의 vtable을 원격으로 저장하는 것이 더 합리적이지만, 우리가 많이 부르는 몇 가지 기능만을 인화하는 것이 더 합리적입니다. Dyno는 Selectors를 사용하여 매우 쉽게 수행 할 수 있습니다. 이는 정책이 적용되는 기능을 사용자 정의하는 데 사용할 수 있습니다.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
using Storage = dyno::sbo_storage< 16 >;
using VTable = dyno::vtable<
dyno::local<dyno::only<decltype( " draw " _s)>>,
dyno::remote<dyno::everything_else>
>;
dyno::poly<Drawable, Storage, VTable> poly_;
}; 이 정의가 주어지면 Vtable은 실제로 두 가지로 나뉩니다. 첫 번째 부분은 drawable 물체에 로컬이며 draw 방법 만 포함합니다. 두 번째 부분은 나머지 방법 (예 : 소멸자)을 보유하는 정적 스토리지의 vtable에 대한 포인터입니다.
Dyno는 dyno::local<> 및 dyno::remote<> 두 가지 vtable 정책을 제공합니다. 이 두 정책은 선택기를 사용하여 사용자 정의해야합니다. 라이브러리에서 지원하는 셀렉터는 dyno::only<functions...> , dyno::except<...> , dyno::everything_else 입니다 ( dyno::everything 으로도 철자 될 수도 있습니다).
개념을 정의 할 때는 종종 개념과 관련된 일부 기능에 대한 기본 정의를 제공 할 수 있습니다. 예를 들어, 기본적으로 draw (있는 경우)라는 멤버 함수를 사용하여 Drawable 개념의 추상적 인 "draw" 메소드를 구현하는 것이 합리적 일 것입니다. 이를 위해 dyno::default_concept_map 사용할 수 있습니다.
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = []( auto const & self, std::ostream& out) { self. draw (out); }
); 이제 일부 유형 T Drawable 개념을 어떻게 충족시키는 지 살펴 보려고 할 때마다 개념 맵이 정의되지 않으면 기본 개념 맵으로 돌아갑니다. 예를 들어, 새로운 유형 Circle 만들 수 있습니다.
struct Circle {
void draw (std::ostream& out) const {
out << " circle " << std::endl;
}
};
f (Circle{}); // prints "circle" Circle Circle 에 대한 개념 맵을 명시 적으로 정의하지 않았지만 자동으로 Drawable 모델입니다. 반면에, 우리가 그러한 개념 맵을 정의한다면 기본 맵보다 우선합니다.
template <>
auto dyno::concept_map<Drawable, Circle> = dyno::make_concept_map(
" draw " _s = [](Circle const & circle, std::ostream& out) {
out << " triangle " << std::endl;
}
);
f (Circle{}); // prints "triangle" 때로는 한 번에 전체 유형 제품군에 대한 개념 맵을 정의하는 것이 유용합니다. 예를 들어, 우리는 std::vector<T> Drawable 모델로 만들 수 있지만 T 스트림에 인쇄 할 수있는 경우에만 가능합니다. 이것은이 비밀 트릭을 사용하여 쉽게 달성 할 수 있습니다.
template < typename T>
auto const dyno::concept_map<Drawable, std::vector<T>, std:: void_t <decltype(
std::cout << std::declval<T>()
)>> = dyno::make_concept_map(
" draw " _s = [](std::vector<T> const & v, std::ostream& out) {
for ( auto const & x : v)
out << x << ' ' ;
}
);
f (std::vector< int >{ 1 , 2 , 3 }) // prints "1 2 3 "
std::vector전혀 수정할 필요가없는 방법에 주목하십시오. 고전적인 다형성으로 어떻게 이것을 할 수 있습니까? 답 : 할 수 있습니다.
Dyno는 임의의 인수 (그러나 하나의 인수 만)에 파견되는 비회원 기능과 기능을 지우는 것을 허용합니다. 이렇게하려면 dyno::function dyno::method 함수를 사용하여 개념을 정의하고 dyno::T 자리 표시자를 사용하여 지워진 인수를 나타냅니다.
// Define the interface of something that can be drawn
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::function< void (dyno::T const &, std::ostream&)>
)) { }; 위에서 사용 된 dyno::T const& 매개 변수는 함수가 호출되는 객체의 유형을 나타냅니다. 그러나 첫 번째 매개 변수 일 필요는 없습니다.
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::function< void (std::ostream&, dyno::T const &)>
)) { };개념의 충족은 개념이 메소드 또는 함수를 사용하는지 여부에 관계없이 변경되지 않지만 기능 구현의 매개 변수가 개념에서 선언 된 기능의 매개 변수와 일치하는지 확인하십시오.
// Define how concrete types can fulfill that interface
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = [](std::ostream& out, T const & self) { self. draw (out); }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ matches the concept definition
); 마지막으로, dyno::poly 에서 function 호출 할 때 Dyno는 어떤 매개 변수를 분명히 전달 해야하는지 추측 할 수 없기 때문에 모든 매개 변수를 명시 적으로 전달해야합니다. 개념에서 dyno::T 자리 표시 자로 선언 된 매개 변수는 dyno::poly 자체를 전달해야합니다.
// Define an object that can hold anything that can be drawn.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out, poly_); }
// ^^^^^ passing the poly explicitly
private:
dyno::poly<Drawable> poly_;
};