C++ インサイト - テンプレートのインスタンス化

Andreas からの今日の投稿は、テンプレートのインスタンス化に関するものです。 C++ Insights は、この自動プロセスをより深く理解するのに大いに役立ちます。

C++ の未来はテンプレートを語ります。したがって、テンプレートをよりよく理解することをお勧めします。

テンプレートのインスタンス化

この時点で免責事項から始めたいと思います。この作業を行うためのツールは他にもあります。インスタンス化されたテンプレートを表示できる Visual Studio のプレビューを見ました。同じことがセベロップにも当てはまります。これは、C++ Insights がここで提供する固有の機能ではありません。 1 つの違いを除いて、入力したすべてのコードの変換が一度に表示されます。すべての!テンプレートだけではありません。

私が話しているのは、私たちの多くが少なくとも一度は経験したと思う状況です。この関数テンプレートは、より大きなものです。インスタンス化される型と、どこから取得されるかを知りたいです。 C++ Insights にとっては簡単なことですが、コンパイラはこれを認識している必要があり、C++ Insights も同様です。

効果的に実行されるコードを表示できることは、教育の際に役立ちます。私を信じる必要はなく、何が起こっているのかを見ることができれば、学生にとって大きな助けになることを経験しました.

テンプレートのインスタンス化の怠惰

C++ Insights が示す良いことの 1 つは、それが示していないことです。コンパイラー (少なくとも C++ Insights が実行される Clang) は、最も効率的なコードを提供することに熱心です。テンプレートに関して言えば、コンパイラは実際に使用される関数またはメソッドに対してのみコードを生成します。決して呼び出されない特定のメソッドを持つクラス テンプレートを持つことができます。ここのように:

template<typename T>
class Apple
{
public:
 Apple() = default;
 
 bool IsGreen() const { return false; }
 bool IsRed() const { return true; }
};

int main()
{
 Apple<int> apple;
 
 if( apple.IsRed()) {}
}

この場合、C++ Insights で確認できるように、コンパイラはそのインスタンス化 (Apple) のメソッド本体を生成しません:

template<typename T>
class Apple
{
public:
 Apple() = default;
 
 bool IsGreen() const { return false; }
 bool IsRed() const { return true; }
};

/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Apple<int>
{ 
 public: 
 // inline constexpr Apple() noexcept = default;
 inline bool IsGreen() const;
 
 inline bool IsRed() const;
 
 // inline constexpr Apple(const Apple<int> &) = default;
 // inline constexpr Apple(Apple<int> &&) = default;
};

#endif


int main()
{
 Apple<int> apple = Apple<int>();
}

メソッドが別のインスタンス化 (Apple<char>) で使用されている場合でも )、int バリアントのコードはありません。もちろん、メソッドは Apple<char> に存在します . C++ Insights で確認してください:

template<typename T>
class Apple
{
public:
 Apple() = default;
 
 bool IsGreen() const { return false; }
 bool IsRed() const { return true; }
};

/* First instantiated from: insights.cpp:13 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Apple<int>
{ 
 public: 
 // inline constexpr Apple() noexcept = default;
 inline bool IsGreen() const;
 
 inline bool IsRed() const;
 
 // inline constexpr Apple(const Apple<int> &) = default;
 // inline constexpr Apple(Apple<int> &&) = default;
};

#endif


/* First instantiated from: insights.cpp:14 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Apple<char>
{ 
 public: 
 // inline constexpr Apple() noexcept = default;
 inline bool IsGreen() const
 {
 return false;
 }
 
 inline bool IsRed() const;
 
 // inline constexpr Apple(const Apple<char> &) = default;
 // inline constexpr Apple(Apple<char> &&) = default;
};

#endif


int main()
{
 Apple<int> apple = Apple<int>();
 Apple<char> cApple = Apple<char>();
 cApple.IsGreen();
}

コンパイラが小さなバイナリを生成するのに役立つので、これは素晴らしいことです。別の見方としては、たとえば、どのコンストラクターが使用されているかなどをデバッグするのに役立つというものです。

C++ Insights で確認できるのは、元のコードのどの行がインスタンス化を引き起こしたかです。これは、特定のインスタンス化が予期されない場合に役立ちます。

クラス テンプレートの引数控除

C++17 と CTAD (クラス テンプレートの引数推定) を使用する場合、取得した型があまり明確でない場合があります。このコードを想定してください (おそらく見やすいと思います):

#include <vector>

int main()
{
 std::vector v{1,2,3};
 std::vector vd{1.0,2.0,3.0};

 //v = vd; // does not compile
}

2 つの std::vector があり、それぞれに 3 つの数値が割り当てられます。これら 2 つのベクトルは実際には同じように見えますが、vd を v に割り当てることはできません。v は int 型で、vd は double 型であることは明らかです。 C++ Insights のかなり簡単なこと:

#include <vector>

int main()
{
 std::vector<int, std::allocator<int> > v = std::vector<int, std::allocator<int> >{std::initializer_list<int>{1, 2, 3}, std::allocator<int>()};
 std::vector<double, std::allocator<double> > vd = std::vector<double, std::allocator<double> >{std::initializer_list<double>{1.0, 2.0, 3.0}, std::allocator<double>()};
}

ここで、ベクトルが実際に持っている型を確認できます。

constexpr の場合

私たちがInsightsについて話している間、私たちのためにそれを行うことができます。以下の例では、stringify があります。 std::string を作成するテンプレート 関数に渡されたパラメータから:

#include <string>
#include <type_traits>

template <typename T>
std::string stringify(T&& t)
{
 if constexpr(std::is_same_v<T, std::string>) {
 return t;
 } else {
 return std::to_string(t);
 }
}

int main()
{
 auto x = stringify(2);
 auto y = stringify(std::string{"Hello"});
}

もちろん、std::string を渡せば この文字列を返すだけです。 constexpr if この関数テンプレート全体を可能にするのに役立ちます。 to_string がないから std::string を取る関数 .通常の if では このコードはコンパイルできません。

では、c-string を渡すとどうなるでしょうか。ここのように:

#include <string>
#include <type_traits>

template <typename T>
std::string stringify(T&& t)
{
 if constexpr(std::is_same_v<T, std::string>) {
 return t;
 } else {
 return std::to_string(t);
 }
}

int main()
{
 auto x = stringify(2);
 auto y = stringify("hello");
}

コンパイルされません。その理由は、to_string も存在しないためです。 文字配列の場合。追加の if を提供することでこれを修正できます この場合:

#include <string>
#include <type_traits>

template <typename T>
std::string stringify(T&& t)
{
 if constexpr(std::is_same_v<T, std::string>) {
 return t;
 } else if constexpr(std::is_array_v< std::remove_reference_t<T> >) {
 return std::string{t};
 } else {
 return std::to_string(t);
 }
}

int main()
{
 auto x = stringify(2);
 auto y = stringify("hello");
}

これでコンパイルされます。 C++ Insights が示すのは、2 つの型のテンプレートのインスタンス化です。しかし、それだけではありません。また、どの if も表示されます -branch はそのインスタンス化で使用されます。よく見ると、別の何かを見つけることができます。 C++ Insights は、else if がないことも示しています。 C++で。 if しかありません そして else .何でこれが大切ですか? constexpr を適用する必要があるため すべての if に -枝。それ以外の場合、constexpr if の場合はランタイムになります else-branch.

C++ Insights に関する情報を彼の人気ブログで共有する機会を与えてくれた Rainer に感謝します!

C++ Insights をお楽しみください。 Patreon になるか、
もちろんコードの寄稿でプロジェクトをサポートできます。

C++ Insights に関するさらなる洞察にご期待ください。次の投稿は Variadic テンプレートについてです。

アンドレアス