development

void_t "개념을 구현할 수 있습니까?"

big-blog 2020. 11. 11. 20:22
반응형

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::declvaldecltype모든 곳. 대신 다음과 같이 후행 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::advanceusing 개념 술어 를 만들고 싶다면 다음과 같이 정의 할 수 있습니다 (지금은 감소 가능한 경우를 무시합니다).

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

반응형