11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

C++のテンプレート制約技術の進化を、SFINAE(C++98〜)からConcepts(C++20)まで追いかける。

SFINAEは「黒魔術」と呼ばれることもあるけど、Conceptsを使えばスッキリ書けるようになる。

パート1: SFINAE

SFINAE = Substitution Failure Is Not An Error(置換失敗はエラーではない)

1. enable_ifによる制約

#include <type_traits>

// 整数型のみ受け付ける
template<typename T, typename = enable_if_t<is_integral_v<T>>>
T double_value(T x) {
    return x * 2;
}

double_value(5);     // OK
// double_value(3.14);  // コンパイルエラー

2. decltype SFINAEによる型検出

// size() メソッドを持つか検出
template<typename T>
auto has_size_impl(int) -> decltype(declval<T>().size(), true_type{});

template<typename T>
false_type has_size_impl(...);

template<typename T>
struct has_size : decltype(has_size_impl<T>(0)) {};

// has_size<vector<int>>::value == true
// has_size<int>::value == false

3. void_tを使った検出イディオム

template<typename, typename = void>
struct has_begin_end : false_type {};

template<typename T>
struct has_begin_end<T, void_t<
    decltype(declval<T>().begin()),
    decltype(declval<T>().end())
>> : true_type {};

4. enable_ifでオーバーロード制御

template<typename T>
enable_if_t<is_arithmetic_v<T>, string>
type_category(T) { return "arithmetic"; }

template<typename T>
enable_if_t<is_class_v<T> && !is_same_v<T, string>, string>
type_category(T) { return "class"; }

template<typename T>
enable_if_t<is_same_v<T, string>, string>
type_category(T) { return "string"; }

パート2: C++20 Concepts

5. 基本的なコンセプト定義

template<typename T>
concept Numeric = is_arithmetic_v<T>;

template<typename T>
concept Integral = is_integral_v<T>;

template<typename T>
concept FloatingPoint = is_floating_point_v<T>;

6. requires式を使ったコンセプト

template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> convertible_to<T>;
};

template<typename T>
concept Printable = requires(T x, ostream& os) {
    { os << x } -> same_as<ostream&>;
};

7. Containerコンセプト

template<typename T>
concept Container = requires(T c) {
    typename T::value_type;
    typename T::iterator;
    { c.begin() } -> same_as<typename T::iterator>;
    { c.end() } -> same_as<typename T::iterator>;
    { c.size() } -> convertible_to<size_t>;
};

8. コンセプトを使った関数

// 制約付きテンプレート
template<Numeric T>
T add(T a, T b) {
    return a + b;
}

// コンテナの合計
template<Container C>
auto container_sum(const C& c) {
    typename C::value_type sum{};
    for (const auto& elem : c) {
        sum += elem;
    }
    return sum;
}

9. requires節

template<typename T>
requires Integral<T>
T factorial_concept(T n) {
    if (n <= 1) return 1;
    return n * factorial_concept(n - 1);
}

10. auto + requires (簡略記法)

// 最も簡潔な書き方
auto process_numeric(Numeric auto x) {
    return x * 2;
}

process_numeric(10);    // OK
process_numeric(3.5);   // OK
// process_numeric("hello");  // エラー

11. コンセプトの組み合わせ

template<typename T>
concept OrderedContainer = Container<T> && requires(T c) {
    { *c.begin() < *c.begin() } -> convertible_to<bool>;
};

12. 制約付きクラス

template<Numeric T>
class NumericWrapper {
    T value;
public:
    constexpr NumericWrapper(T v) : value(v) {}
    constexpr T get() const { return value; }
    constexpr NumericWrapper operator+(const NumericWrapper& other) const {
        return NumericWrapper(value + other.value);
    }
};

NumericWrapper<int> w1(10);    // OK
// NumericWrapper<string> w2;  // エラー

SFINAE vs Concepts 比較

SFINAEの問題点

// 読みにくい...
template<typename T, typename = enable_if_t<
    is_integral_v<T> && !is_same_v<T, bool>>>
T process(T x) { return x * 2; }

Conceptsなら

// 読みやすい!
template<typename T>
concept IntegralButNotBool = integral<T> && !same_as<T, bool>;

auto process(IntegralButNotBool auto x) {
    return x * 2;
}

実行結果

=== SFINAE基本 ===
double_value(5): 10

=== 型検出 ===
has_size<vector<int>>: 1
has_size<int>: 0
has_begin_end<vector<int>>: 1
has_begin_end<int>: 0

=== オーバーロード制御 ===
type_category(42): arithmetic
type_category(3.14): arithmetic
type_category(string): string
type_category(vector<int>): class

=== 基本コンセプト ===
Numeric<int>: 1
Numeric<string>: 0
Integral<int>: 1
Integral<double>: 0

=== コンセプト関数 ===
add(3, 4): 7
add(1.5, 2.5): 4
container_sum(vector): 15

=== コンテナコンセプト ===
Container<vector<int>>: 1
Container<int>: 0
OrderedContainer<vector<int>>: 1

エラーメッセージの改善

SFINAE

error: no matching function for call to 'process'
note: candidate template ignored: requirement 
'std::is_integral_v<std::basic_string<char>>' was not satisfied

Concepts

error: constraints not satisfied for class template 'NumericWrapper'
note: because 'std::string' does not satisfy 'Numeric'
note: because 'is_arithmetic_v<std::string>' evaluated to false

まとめ

特徴 SFINAE Concepts
可読性 低い 高い
エラーメッセージ 難解 明確
再利用性 難しい 簡単
C++バージョン C++98〜 C++20〜

C++20が使えるなら、Conceptsを積極的に使いましょう!

11
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?