web-dev-qa-db-ja.com

c ++汎用コンパイル時forループ

一部のコンテキストでは、コンパイル時にforループを評価/展開することが有用/必要になる場合があります。たとえば、Tupleの要素を反復処理するには、テンプレートintパラメータIに依存するstd::get<I>を使用する必要があるため、次のようにする必要があります。コンパイル時に評価されます。コンパイル再帰を使用すると、たとえば herehere 、特にstd::Tuplehere のように、特定の問題を解決できます。

ただし、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)

質問は次のとおりです。

  1. 最初の引数としてテンプレート関数を受け入れるようにcompile_time_forを書く方法はありますか?
  2. 質問1が正の場合、ルーチンがすべてのループ反復でOperatorType<start>型のオブジェクトを作成するという事実により、最初の作業コードにオーバーヘッドがありますか?
  3. 今後のc++20にコンパイル時forループのような機能を導入する予定はありますか?
14
francesco
  1. 最初の引数としてテンプレート関数を受け入れるようにcompile_time_forを記述する方法はありますか?

短い答え:いいえ。

長い答え:テンプレート関数はオブジェクトではなく、オブジェクトのコレクションであり、引数として、オブジェクトのコレクションではなく、オブジェクトとして関数に渡すことができます。

このタイプの問題に対する通常の解決策は、テンプレート関数をクラス内にラップし、クラスのオブジェクト(または、関数が静的メソッドとしてラップされている場合は単にタイプ)を渡すことです。これがまさに、作業コードで採用したソリューションです。

  1. 質問1が肯定的である場合、ルーチンがすべてのループ反復でタイプOperatorTypeのオブジェクトを作成するという事実により、最初の作業コードにオーバーヘッドがありますか?

質問1は否定的です。

  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 */ }
_
6
max66

最後のコードサンプルを修正する方法についての質問に答えます。

コンパイルされない理由はここにあります:

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;
}
3
Dmitry Gordon