void_t "개념을 구현할 수 있습니까?"
나는 Walter Brown의 CppCon2014의 템플릿 메타 프로그래밍에 대한 두 번째 부분을보고 있었는데 , 그 동안 그는 그의 소설 void_t<>
구조 의 사용에 대해 논의했습니다 . 그의 발표에서 Peter Sommerlad는 그에게 내가 이해하지 못하는 질문을했습니다. (링크는 질문으로 직접 연결되며 토론중인 코드는 그 직전에 발생했습니다)
Sommerlad가 물었다
월터, 그게 우리가 지금 당장 개념을 라이트로 구현할 수 있다는 뜻인가요?
Walter가 응답 한
오 예! 나는 그것을했다… 그것은 완전히 같은 구문을 가지고 있지 않다.
이 교환은 Concepts Lite에 관한 것임을 이해했습니다. 이 패턴은 정말 그 다재 다능? 어떤 이유로 든 나는 그것을 보지 못하고있다. 누군가 이와 같은 모습을 설명 (또는 스케치) 할 수 있습니까? 이것은 단지 enable_if
특성을 정의하는 것입니까, 아니면 질문자가 언급 한 것이 무엇입니까?
void_t
다음과 같이 템플릿을 정의한다 :
template<class ...> using void_t = void;
그는 이것을 사용하여 유형 문이 잘 형성되었는지 감지하고이를 사용하여 is_copy_assignable
유형 특성 을 구현합니다 .
//helper type
template<class T>
using copy_assignment_t
= decltype(declval<T&>() = declval<T const&>());
//base case template
template<class T, class=void>
struct is_copy_assignable : std::false_type {};
//SFINAE version only for types where copy_assignment_t<T> is well-formed.
template<class T>
struct is_copy_assignable<T, void_t<copy_assignment_t<T>>>
: std::is_same<copy_assignment_t<T>,T&> {};
강연 때문에이 예제가 어떻게 작동하는지 이해하지만 여기에서 Concepts Lite와 같은 것으로 어떻게 이동하는지는 모르겠습니다.
예, concepts lite는 기본적으로 SFINAE를 드레스합니다. 또한 더 나은 오버로딩을 허용하기 위해 더 깊은 인트로 스펙 션을 허용합니다. 그러나 개념 술어가로 정의 된 경우에만 작동합니다 concept bool
. 개선 된 오버로딩은 현재 개념 술어에서 작동하지 않지만 조건부 오버로딩을 사용할 수 있습니다. C ++ 14에서 조건자를 정의하고 템플릿을 제한하고 함수를 오버로드하는 방법을 살펴 보겠습니다. 이것은 다소 길지만 C ++ 14에서이를 수행하는 데 필요한 모든 도구를 만드는 방법에 대해 설명합니다.
술어 정의
첫째, 모든와 술어를 읽을 종류의 추악한입니다 std::declval
및 decltype
모든 곳. 대신 다음과 같이 후행 decltype (Eric Niebler의 블로그 게시물 에서 )을 사용하여 함수를 제한 할 수 있다는 사실을 활용할 수 있습니다 .
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
따라서 ++x
가 유효하지 않으면 requires_
멤버 함수를 호출 할 수 없습니다. 따라서 다음을 사용하여 호출 models
가능한지 확인 하는 특성을 만들 수 있습니다 .requires_
void_t
template<class Concept, class Enable=void>
struct models
: std::false_type
{};
template<class Concept, class... Ts>
struct models<Concept(Ts...), void_t<
decltype(std::declval<Concept>().requires_(std::declval<Ts>()...))
>>
: std::true_type
{};
제한 템플릿
따라서 개념에 따라 템플릿을 제한하려면을 사용해야 enable_if
하지만이 매크로를 사용하여 더 깔끔하게 만들 수 있습니다.
#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
따라서 개념에 increment
따라 제한 되는 함수를 정의 할 수 있습니다 Incrementable
.
template<class T, REQUIRES(models<Incrementable(T)>())>
void increment(T& x)
{
++x;
}
따라서 increment
가 아닌 것으로 호출 하면 다음 Incrementable
과 같은 오류가 발생합니다.
test.cpp:23:5: error: no matching function for call to 'incrementable'
incrementable(f);
^~~~~~~~~~~~~
test.cpp:11:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo]
template<class T, REQUIRES(models<Incrementable(T)>())>
^
오버로딩 함수
이제 오버로딩을하려면 조건부 오버로딩을 사용합니다. std::advance
using 개념 술어 를 만들고 싶다면 다음과 같이 정의 할 수 있습니다 (지금은 감소 가능한 경우를 무시합니다).
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void advance(Iterator& it, int n)
{
it += n;
}
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void advance(Iterator& it, int n)
{
while (n--) ++it;
}
그러나 이것은 반복자 concept bool
와 함께 사용될 때 모호한 오버로드를 유발합니다 (컨셉 라이트에서 이것은 우리가 술어를 a의 다른 술어를 참조하도록 변경하지 않는 한 여전히 모호한 오버로드입니다 ) std::vector
. 우리가 원하는 것은 조건부 오버로딩을 사용하여 할 수있는 호출을 주문하는 것입니다. 다음과 같이 작성하는 것으로 생각할 수 있습니다 (유효한 C ++가 아닙니다).
template<class Iterator>
void advance(Iterator& it, int n) if (models<Advanceable(Iterator, int)>())
{
it += n;
}
else if (models<Incrementable(Iterator)>())
{
while (n--) ++it;
}
따라서 첫 번째 함수가 호출되지 않으면 다음 함수가 호출됩니다. 따라서 두 가지 기능에 대해 구현하여 시작하겠습니다. basic_conditional
두 개의 함수 객체를 템플릿 매개 변수로 받아들이는 라는 클래스를 만들 것입니다 .
struct Callable
{
template<class F, class... Ts>
auto requires_(F&& f, Ts&&... xs) -> decltype(
f(std::forward<Ts>(xs)...)
);
};
template<class F1, class F2>
struct basic_conditional
{
// We don't need to use a requires clause here because the trailing
// `decltype` will constrain the template for us.
template<class... Ts>
auto operator()(Ts&&... xs) -> decltype(F1()(std::forward<Ts>(xs)...))
{
return F1()(std::forward<Ts>(xs)...);
}
// Here we add a requires clause to make this function callable only if
// `F1` is not callable.
template<class... Ts, REQUIRES(!models<Callable(F1, Ts&&...)>())>
auto operator()(Ts&&... xs) -> decltype(F2()(std::forward<Ts>(xs)...))
{
return F2()(std::forward<Ts>(xs)...);
}
};
따라서 이제 함수를 대신 함수 객체로 정의해야합니다.
struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};
struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};
static conditional<advance_advanceable, advance_incrementable> advance = {};
이제 다음과 같이 사용하려고하면 std::vector
:
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
auto iterator = v.begin();
advance(iterator, 4);
std::cout << *iterator << std::endl;
컴파일하고 출력합니다 5
.
그러나 std::advance
실제로는 세 개의 오버로드가 있으므로을 사용하여 재귀를 사용하는 여러 함수에 대해 작동 basic_conditional
하는 구현을 수행 할 수 있습니다 conditional
.
template<class F, class... Fs>
struct conditional : basic_conditional<F, conditional<Fs...>>
{};
template<class F>
struct conditional<F> : F
{};
이제 다음 std::advance
과 같이 전체를 작성할 수 있습니다 .
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
struct Decrementable
{
template<class T>
auto requires_(T&& x) -> decltype(--x);
};
struct Advanceable
{
template<class T, class I>
auto requires_(T&& x, I&& i) -> decltype(x += i);
};
struct advance_advanceable
{
template<class Iterator, REQUIRES(models<Advanceable(Iterator, int)>())>
void operator()(Iterator& it, int n) const
{
it += n;
}
};
struct advance_decrementable
{
template<class Iterator, REQUIRES(models<Decrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
}
};
struct advance_incrementable
{
template<class Iterator, REQUIRES(models<Incrementable(Iterator)>())>
void operator()(Iterator& it, int n) const
{
while (n--) ++it;
}
};
static conditional<advance_advanceable, advance_decrementable, advance_incrementable> advance = {};
Lambda로 오버로드
그러나 추가로 함수 객체 대신 람다를 사용하여 작성하는 데 도움이 될 수 있습니다. 따라서이 STATIC_LAMBDA
매크로를 사용 하여 컴파일 타임에 람다를 생성합니다.
struct wrapper_factor
{
template<class F>
constexpr wrapper<F> operator += (F*)
{
return {};
}
};
struct addr_add
{
template<class T>
friend typename std::remove_reference<T>::type *operator+(addr_add, T &&t)
{
return &t;
}
};
#define STATIC_LAMBDA wrapper_factor() += true ? nullptr : addr_add() + []
다음과 같은 make_conditional
기능을 추가하십시오 constexpr
.
template<class... Fs>
constexpr conditional<Fs...> make_conditional(Fs...)
{
return {};
}
이제 다음 advance
과 같이 함수를 작성할 수 있습니다 .
constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Advanceable(decltype(it), int)>()))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Decrementable(decltype(it))>()))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(models<Incrementable(decltype(it))>()))
{
while (n--) ++it;
}
);
함수 객체 버전을 사용하는 것보다 조금 더 간결하고 읽기 쉽습니다.
또한 추악함 modeled
을 줄이는 함수를 정의 할 수 있습니다 decltype
.
template<class Concept, class... Ts>
constexpr auto modeled(Ts&&...)
{
return models<Concept(Ts...)>();
}
constexpr const advance = make_conditional(
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Advanceable>(it, n)))
{
it += n;
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Decrementable>(it)))
{
if (n > 0) while (n--) ++it;
else
{
n *= -1;
while (n--) --it;
}
},
STATIC_LAMBDA(auto& it, int n, REQUIRES(modeled<Incrementable>(it)))
{
while (n--) ++it;
}
);
Finally, if you are interested in using existing library solutions(rather than rolling your own like I've shown). There is the Tick library that provides a framework for defining concepts and constraining templates. And the Fit library can handle the functions and overloading.
참고URL : https://stackoverflow.com/questions/26513095/void-t-can-implement-concepts
'development' 카테고리의 다른 글
테이블 레이아웃이있는 테이블 : 고정; (0) | 2020.11.11 |
---|---|
엔터티 프레임 워크 코드는 먼저 "구별 자"열을 만듭니다. (0) | 2020.11.11 |
** glob 문자는 무엇입니까? (0) | 2020.11.11 |
Jupyter 노트북에서 프로그래밍 방식으로 마크 다운 출력을 생성하는 방법은 무엇입니까? (0) | 2020.11.11 |
파이썬에서 블록 앞의 콜론의 목적은 무엇입니까? (0) | 2020.11.11 |