一部のコンテキストでは、コンパイル時にfor
ループを評価/展開することが有用/必要になる場合があります。たとえば、Tuple
の要素を反復処理するには、テンプレートint
パラメータI
に依存するstd::get<I>
を使用する必要があるため、次のようにする必要があります。コンパイル時に評価されます。コンパイル再帰を使用すると、たとえば here 、 here 、特にstd::Tuple
here のように、特定の問題を解決できます。
ただし、genericコンパイル時for
ループを実装する方法に興味があります。
次のc++17
コードはこのアイデアを実装しています
#include <utility>
#include <Tuple>
#include <string>
#include <iostream>
template <int start, int end, template <int> class OperatorType, typename... Args>
void compile_time_for(Args... args)
{
if constexpr (start < end)
{
OperatorType<start>()(std::forward<Args>(args)...);
compile_time_for<start + 1, end, OperatorType>(std::forward<Args>(args)...);
}
}
template <int I>
struct print_Tuple_i {
template <typename... U>
void operator()(const std::Tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::Tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3, print_Tuple_i>(x);
return 0;
}
コードは機能しますが、各反復でインスタンス化されるテンプレートクラスではなく、ルーチンcompile_time_for
にテンプレート関数を単純に提供できるほうがよいでしょう。
ただし、次のようなコードはc++17
でコンパイルされません
#include <utility>
#include <Tuple>
#include <string>
#include <iostream>
template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
{
if constexpr (start < end)
{
f<start>(std::forward<Args>(args)...);
compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
}
}
template <int I, typename... U>
void myprint(const std::Tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
int main()
{
std::Tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>(myprint, x);
return 0;
}
Gcc 7.3.0とオプションstd=c++17
では、最初のエラーは
for2.cpp:7:25: error: ‘auto’ parameter not permitted in this context
void compile_time_for(F f, Args... args)
質問は次のとおりです。
compile_time_for
を書く方法はありますか?OperatorType<start>
型のオブジェクトを作成するという事実により、最初の作業コードにオーバーヘッドがありますか?c++20
にコンパイル時forループのような機能を導入する予定はありますか?
- 最初の引数としてテンプレート関数を受け入れるようにcompile_time_forを記述する方法はありますか?
短い答え:いいえ。
長い答え:テンプレート関数はオブジェクトではなく、オブジェクトのコレクションであり、引数として、オブジェクトのコレクションではなく、オブジェクトとして関数に渡すことができます。
このタイプの問題に対する通常の解決策は、テンプレート関数をクラス内にラップし、クラスのオブジェクト(または、関数が静的メソッドとしてラップされている場合は単にタイプ)を渡すことです。これがまさに、作業コードで採用したソリューションです。
- 質問1が肯定的である場合、ルーチンがすべてのループ反復でタイプOperatorTypeのオブジェクトを作成するという事実により、最初の作業コードにオーバーヘッドがありますか?
質問1は否定的です。
- 今後のc ++ 20でコンパイル時forループのような機能を導入する計画はありますか?
この質問に答えるのに十分なC++ 20を知りませんが、関数のセットを渡さないと思います。
とにかく、C++ 14以降、_std::make_index_sequence
_/_std::index_sequence
_を使用して、一種のコンパイル時forループを実行できます。
たとえば、touple値をmyprint()
関数の外で抽出することを受け入れる場合は、ラムダ内にラップして次のように書くことができます(C++ 17テンプレートフォールディングも使用します。C++ 14ではもう少し複雑です)
_#include <utility>
#include <Tuple>
#include <string>
#include <iostream>
template <typename T>
void myprint (T const & t)
{ std::cout << t << " "; }
template <std::size_t start, std::size_t ... Is, typename F, typename ... Ts>
void ctf_helper (std::index_sequence<Is...>, F f, std::Tuple<Ts...> const & t)
{ (f(std::get<start + Is>(t)), ...); }
template <std::size_t start, std::size_t end, typename F, typename ... Ts>
void compile_time_for (F f, std::Tuple<Ts...> const & t)
{ ctf_helper<start>(std::make_index_sequence<end-start>{}, f, t); }
int main()
{
std::Tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>([](auto const & v){ myprint(v); }, x);
return 0;
}
_
関数内でタプル要素(またはタプル要素)を本当に抽出したい場合は、最初の例を次のように変換するのが私が想像できる最良の方法です。
_#include <utility>
#include <Tuple>
#include <string>
#include <iostream>
template <std::size_t start, template <std::size_t> class OT,
std::size_t ... Is, typename... Args>
void ctf_helper (std::index_sequence<Is...> const &, Args && ... args)
{ (OT<start+Is>{}(std::forward<Args>(args)...), ...); }
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
template <std::size_t I>
struct print_Tuple_i
{
template <typename ... U>
void operator() (std::Tuple<U...> const & x)
{ std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::Tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0u, 3u, print_Tuple_i>(x);
return 0;
}
_
-編集-
OPは尋ねます
最初のコードよりもindex_sequenceを使用する利点はありますか?
私は専門家ではありませんが、この方法で再帰を回避できます。コンパイラーには、テンプレートの観点から、再帰制限があります。このようにして、それらを回避します。
また、テンプレートパラメータを_
end > start
_に設定した場合、コードはコンパイルされません。 (コンパイラーにループがインスタンス化されているかどうかを判断させたい状況を想像できます)
_start > end
_の場合、私のコードがコンパイルされないことを意味していると思います。
悪い点は、この問題についてのチェックがないため、この場合もコンパイラーがコードをコンパイルしようとすることです。とても出会う
_ std::make_index_sequence<end-start>{}
_
ここで、_end - start
_は負の数値ですが、符号なしの数値を予期するテンプレートで使用されます。したがって、_end - start
_は非常に大きな正の数になり、これが問題を引き起こす可能性があります。
static_assert()
内にcompile_time_for()
を課すこの問題を回避できます
_template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{
static_assert( end >= start, "start is bigger than end");
ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...);
}
_
または、SFINAEを使用して機能を無効にすることもできます
_template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
std::enable_if_t<(start <= end)> compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
_
必要に応じて、SFINAEを使用して、オーバーロードされたcompile_time_for()
バージョンを追加し、_end < start
_ケースを管理できます
_template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename ... Args>
std::enable_if_t<(start > end)> compile_time_for (Args && ...)
{ /* manage the end < start case in some way */ }
_
最後のコードサンプルを修正する方法についての質問に答えます。
コンパイルされない理由はここにあります:
template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
/\
Fはテンプレートです。テンプレートパラメータを置換しないと、テンプレートクラスのオブジェクトを作成できません。例えば。 std::vector
タイプのオブジェクトを持つことはできませんが、std::vector<int>
のオブジェクトを持つことはできます。テンプレートoperator()を使用してF
ファンクタを作成することをお勧めします。
#include <utility>
#include <Tuple>
#include <string>
#include <iostream>
template <int start, int end, typename F, typename... Args>
void compile_time_for(F f, Args... args)
{
if constexpr (start < end)
{
f.template operator()<start>(std::forward<Args>(args)...);
compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
}
}
struct myprint
{
template <int I, typename... U>
void operator()(const std::Tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::Tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>(myprint(), x);
return 0;
}