コンテンツにスキップ

テンプレート#

イントロダクション#

テンプレートを使用すると、クラスまたは関数の操作を定義し、その操作が具体的にどの型で動作するかをユーザーが指定できるようになります。 なんのこっちゃって感じですね実際の例を見て確認していきます。

#include <iostream>

template <typename T>
T add(const T& a, const T& b)
{
    const T c = a + b;
    return c;
}

int main(int argc, const char * argv[]) {
    std::cout << add<double>(0.2,0.3) << std::endl;
    return 0;
}

typename キーワードは、このパラメーターが型のプレースホルダーであることを示します。 関数が呼び出されると、コンパイラは、T のすべてのインスタンスを、ユーザーによって指定されるか、コンパイラによって推測される具体的な型引数に置き換えます。

テンプレート制約#

例のadd関数はどんな型でも受け取れてしまいます。では下記のような構造体を入れるとどうなるでしょう。

struct point3{
    double x, y, z;
}
コンパイルエラーになります。構造体同士を直接足し算することはできないからです。それらを区別する実装をするにはコンセプトイディオドムを書く必要があります。下記の例はデフォルトで用意されているイディオドムと自分て定義したものを使用した例です。

#include <iostream>

template <typename T, typename = void>
struct IsLikePoint3 : std::false_type {};

template <typename T>
struct IsLikePoint3<
    T, std::void_t<
        decltype(std::declval<T>().x),
        decltype(std::declval<T>().y),
        decltype(std::declval<T>().z)
    >>
    : std::true_type {};

template <
  typename T,
  std::enable_if_t<std::conjunction_v<std::is_scalar<T>>, std::nullptr_t> =
    nullptr>
T add(const T& a, const T& b)
{
    const T c = a + b;
    return c;
}

template <
  typename T,
  std::enable_if_t<std::conjunction_v<IsLikePoint3<T>>, std::nullptr_t> =
    nullptr>
T add(const T& a, const T& b)
{
    auto c = T();
    c.x = a.x + b.x;
    c.y = a.y + b.y;
    c.z = a.z + b.z;
    return c;
}

struct point3{
    double x, y, z;
};

int main(int argc, const char * argv[]) {
    double x1 = 0.2;
    double x2 = 0.4;
    point3 p1 = {0.1,0.2,0.3};
    point3 p2 = {0.3,0.2,0.1};

    auto resultx = add(x1,x2);
    auto resultp = add(p1,p2);

    std::cout << resultx << std::endl;
    std::cout << resultp.x << "," << resultp.y << "," << resultp.z << std::endl;
    return 0;
}