Walter Brownのテンプレートメタプログラミングに関するCppCon2014トーク の第2部を見ていました。その間、彼は小説void_t<>
構築。プレゼンテーション中に、Peter Sommerladが私によくわからない質問をしました。 (リンクは質問に直接移動し、議論中のコードはその直前に行われました)
Sommerladは尋ねた
ウォルター、それは今、私たちが今すぐ概念ライトを実際に実装できることを意味しますか?
ウォルターが応答した
そうそう!私はそれをやった...それはまったく同じ構文を持っていません。
このやり取りはConcepts Liteに関するものであると理解しました。このパターンは本当にthat多目的ですか?何らかの理由で、私はそれを見ていません。誰かがこのようなものがどのように見えるかを説明(またはスケッチ)できますか?これはほぼ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のようなものに到達する方法はわかりません。
はい、コンセプトライトは基本的にSFINAEをドレスアップします。さらに、より深いイントロスペクションにより、オーバーロードを改善できます。ただし、概念述語がconcept bool
として定義されている場合にのみ機能します。改善されたオーバーロードは、現在の概念の述部では機能しませんが、条件付きオーバーロードを使用できます。 C++ 14で述語を定義し、テンプレートを制約し、関数をオーバーロードする方法を見てみましょう。これはちょっと長いですが、C++ 14でこれを実現するために必要なすべてのツールを作成する方法について説明します。
最初に、すべてのstd::declval
とdecltype
をすべて含む述語を読むのはちょっとugいです。代わりに、次のように、末尾のdecltypeを使用して関数を制約できるという事実を利用できます(Eric Nieblerのブログ投稿 here )。
struct Incrementable
{
template<class T>
auto requires_(T&& x) -> decltype(++x);
};
したがって、++x
が無効な場合、requires_
メンバー関数は呼び出しできません。したがって、requires_
がvoid_t
を使用して呼び出し可能かどうかを確認するmodels
特性を作成できます。
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;
}
ただし、これによりあいまいなオーバーロードが発生します(概念ライトでは、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つの機能を実装することから始めましょう。テンプレートパラメータとして2つの関数オブジェクトを受け入れる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
には実際に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() + []
constexpr
であるmake_conditional
関数を追加します。
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
のaさを軽減する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 ライブラリは関数とオーバーロードを処理できます。