69

テンプレート メタプログラミングに関する Walter Brown の CppCon2014 トークの第 2 部を見ていましたvoid_t<>。Peter Sommerlad はプレゼンテーション中に、私にはよくわからない質問をしました。(リンクは質問に直接行きます。議論中のコードはその直前に行われました)

サマーラッドは尋ねた

ウォルター、それは実際にコンセプトライトを今すぐ実装できるということですか?

ウォルターが答えた

そうそう!私はそれをやった...それはまったく同じ構文を持っていません。

このやり取りは 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 のようなものに至る方法がわかりません。

4

1 に答える 1

120

はい、concept lite は基本的に SFINAE をドレスアップします。さらに、より深いイントロスペクションが可能になり、より良いオーバーロードが可能になります。ただし、概念述語が として定義されている場合にのみ機能しconcept boolます。改良されたオーバーロードは、現在の概念の述語では機能しませんが、条件付きオーバーロードは使用できます。C++14 で述語を定義し、テンプレートを制約し、関数をオーバーロードする方法を見てみましょう。少し長くなりますが、C++14 でこれを実現するために必要なすべてのツールを作成する方法について説明します。

述語の定義

まず、述語を all thestd::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::advance場合、次のように定義できます (今のところ、減分可能なケースは無視します)。

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

ただし、これはあいまいなオーバーロードを引き起こします (概念ライトでは、述語を a の他の述語を参照するように変更しない限り、これはまだあいまいなオーバーロードになりますconcept bool) 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;
}

したがって、最初の関数が呼び出されない場合、次の関数が呼び出されます。それでは、2 つの関数に実装することから始めましょう。basic_conditionalテンプレート パラメーターとして 2 つの関数オブジェクトを受け入れるというクラスを作成します。

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実際には 3 つのオーバーロードがあるため、 を使用して、再帰を使用する任意の数の関数に対して機能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 = {};

ラムダによるオーバーロード

ただし、さらに、関数オブジェクトの代わりにラムダを使用して記述することもできます。これにより、記述がよりクリーンになります。したがって、この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;
    }
);

最後に、既存のライブラリ ソリューションを使用することに興味がある場合 (私が示したように自分で作成するのではなく)。概念を定義し、テンプレートを制約するためのフレームワークを提供するTickライブラリがあります。また、Fitライブラリは関数とオーバーロードを処理できます。

于 2014-10-23T16:56:29.730 に答える