読者です 読者をやめる 読者になる 読者になる

Ruby で mixin したモジュールを削除する gem をつくった

Ruby

Rubyメタプログラミングをやっているとどうしても『mixin したモジュールを削除したい』という欲求にかられるのでつくりました。

インストール

$ gem install unmixer

使い方

require "unmixer"

# Unmixer は refinements を使用してるので using して使用します
using Unmixer

module M1; end
module M2; end
module M3; end

class X
    include M1
    prepend M2
end

p X.ancestors
# => [M2, X, M1, Object, Kernel, BasicObject]

# include したモジュールを削除
X.instance_eval { uninclude M1 }
p X.ancestors
# => [M2, X, Object, Kernel, BasicObject]

# include したモジュール以外は削除できない
X.instance_eval { uninclude M2 }
p X.ancestors
# => [M2, X, Object, Kernel, BasicObject]


# prepend したモジュールを削除
X.instance_eval { unprepend M2 }
p X.ancestors
# => [X, Object, Kernel, BasicObject]


X.extend M3
p X.singleton_class.ancestors
# => [#<Class:X>, M3, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

# extend したモジュールを削除
X.unextend M3
p X.singleton_class.ancestors
# => [#<Class:X>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]


# #extend にブロックを渡した場合、ブロック内でのみ mixin される
X.extend M1 do
    # mixin only in block.
    p X.singleton_class.ancestors
    # => [#<Class:X>, M1, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
    p X.singleton_class.ancestors.include? M1
    # => true
end
p X.singleton_class.ancestors
# => [#<Class:X>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
p X.singleton_class.ancestors.include? M1
# => false

元々は uninclude という gem があったのですが、こちらは比較的新しい Ruby では動作しなかったのでそれを焼き直した形になります。

現状は、Ruby 2.1 〜2.4 であれば動作すると思います。
ただ、実装が結構無理なりなので今後のサポートや意図しない動作が発生する可能性はあります。
今回初めて Ruby で C拡張のコードを書いたんですが、やはり C言語つらいですね…。
最初は C 側で全部を実装していたんですが、途中でつらくなったので C 側は最小限の処理のみ実装して結局 Ruby 側でガシガシ実装しました。
多少不安なところはあるんですが、やはり mixin を削除できる機能はかなり便利です。
言語仕様側で導入されないかなあ…。

【初心者C++er Advent Calendar 2016 21日目】C++ でプリプロセスマクロを使う場面

C++

初心者C++er Advent Calendar 2016 21日目の記事です。
さて、巷ではマクロを使用したコードを公開すると『static const を使え』や『typedef しろ』、『関数テンプレートでおk』などと斧が飛んでくる突っ込まれることが多いです。
これは C++ では『基本的にはマクロを使うべきではなく』さらに『マクロを使わないでよいコード』に直せることが多いからです。
では逆に『マクロを使うべきコード』というのはどういうものなのでしょうか。
この記事では『マクロを使うべきコード』の実例をいくつか紹介してみたいと思います。

[インクルードガード]

ヘッダーファイルを記述する際に『重複インクルードを防止するため』にインクルードガードという技法が使用されれます。
これは #ifndef を利用して『一度だけヘッダーファイルが読み込まれる』ような仕組みです。

// hoge.h
#ifndef HOGE_H
#define HOGE_H

// ここに実装を記述する

#endif    // HOGE_H

これにより複数回 #include "hoge.h" しても一度だけ定義が読み込まれます。
ただ、最近は同等の機能である #pragma once を実装してるコンパイラもあるので、そちらを使用している人もいます。

[キーワードなどを置き換える]

さて、次のように constexpr を使ったコードがあるとします。

constexpr auto
plus(int a, int b){
    return a + b;
}

constexpr auto
minus(int a, int b){
    return a - b;
}

static_assert(plus(1, 2) == 3, "");
static_assert(minus(4, 5) == -1, "");

これは C++11 に対応してるコンパイラであれば問題なく動作します。
しかし、環境によってはどうしても C++03 に対応しなければならないこともあります。
こういう場合、constexpr というキーワードをマクロに置き換えることで、複数の環境に柔軟に対応する事が出来ます。

// C++11 以上ならば
#if __cplusplus >= 201103L
    // constexpr をマクロに置き換える
   #define CONSTEXPR constexpr
#else
    // C++11 以上に対応してないなら空のマクロを定義
   #define CONSTEXPR
#endif

// constexpr をマクロに置き換える
CONSTEXPR int
plus(int a, int b){
    return a + b;
}

CONSTEXPR int
minus(int a, int b){
    return a - b;
}

#if __cplusplus >= 201103L
static_assert(plus(1, 2) == 3, "");
static_assert(minus(4, 5) == -1, "");
#endif

このように『マクロ以外』で置き換えることが出来ないようなワークアラウンドに対応する場合に利用できます。

[コンパイルするときに挙動を変更したい場合]

例えば、次のように『デバッグコードを仕込んだコード』があるとします。

int
plus(int a, int b){
// DEBUG が定義されている時のみログを出力したい
#ifndef DEBUG
    std::cout << a << std::endl;
    std::cout << b << std::endl;
#endif
    return a + b;
}

このように『マクロを使用すること』でコンパイル時に『挙動を変更する』事が出来ます

# DEBUG マクロを定義してコンパイルする
$ g++ main.cpp -DDEBUG
$ ./a
1
2
3

gcc だとこんな感じでコンパイル時にマクロを定義する事が出来ます。

[まとめ]

と、言う感じでパッと思いついたものを上げてみました。
他にも使うべき場面はありますが、初心者向けならこのあたりでしょうか。
C++ では『マクロを使うべきではないコード』もたくさんありますが『マクロでしか実装出来ないコード』もまた存在します。
マクロは用法・用量を守って正しくお使いください。

Ruby 2.4 で refinements の制限が緩和された

Ruby

さて、先日 Ruby 2.4 がリリースされました。

Ruby 2.4.0 リリース

[refinements の制限を緩和]

Ruby 2.4 では refinements の制限が緩和され、#send からも refine されているメソッドが呼び出せるようになりました。

class X; end

module Ex
    refine X do
        def homu
            p "homu"
        end
    end
end

using Ex

x = X.new

x.send :homu
# => "homu"

日頃から refinements クソだ!と言ってる勢としては『やっと対応されたか…』という感じ。
今後も使い勝手がよくなるようにここら辺の制限の調整がすすむといいですねえ…。
あとリリース前に Symbol#to_proc 内からも呼び出させるようになると聞いたんですが、こっちはまだエラーでした。

# Error: undefined method `homu' for #<X:0x005564eebeba60> (NoMethodError)
:homu.to_proc.call x

【初心者C++er Advent Calendar 2016 20日目】関数テンプレートの呼び出しに優先順位をつける

C++

初心者C++er Advent Calendar 2016 20日目の記事です。
クリスマスが終わって『もはや Advent Calendar とは…』となってるんですが、まあ埋まってないより埋まってるほうがいいかなーと。
今回は初心者向けかどうか怪しい内容ですが、ちょっとしたハックでも。
なお、この記事は SFINAE を知ってる事が前提の記事になります。

[コード]

さて、次のように SFINAE を利用して『a + aができる型(値)のみ』受け付けたい関数テンプレートを定義したいことがあると思います。

template<typename T>
auto
twice(T a)
// decltype() 内で式を記述することで a + a を要求する
->decltype(a + a){
    return a + a;
}

// 42 + 42 は有効なので OK
twice(42);         // 84

// 3.14f + 3.14f も有効なので OK
twice(3.14f);      // 6.28

// "homu" + "homu" は無効なので NG
twice("homu");

まあここまではよくあるコードだと思います。

[問題]

このような関数テンプレートを定義した場合に『それ以外にマッチする関数』も定義したいことがあると思います。

// a + a が有効でない引数の場合に呼び出す関数を定義
template<typename T>
auto
twice(T t){
    return t;
}

twice("homu");        // "homu"

これでよかったよかった…とはならずに、次のように『a + aができる型(値)』を渡した場合にエラーになります。

// Error: call of overloaded 'twice(int)' is ambiguous
twice(42);

http://melpon.org/wandbox/permlink/GqFem4RxFct7gvqF

これは twice(int) 時に『両方の関数に該当するため』呼び出しが解決できないためです。

[解決]

解決策はいくつかあるんですが、次のようにして『オーバーロードを解決するため引数』を追加することで比較的簡単に回避することが出来ます。

// 引数に int を追加する
template<typename T>
auto
twice(int, T a)
->decltype(a + a){
    return a + a;
}

// 引数に bool を追加する
template<typename T>
auto
twice(bool, T t){
    return t;
}

// int 型の値を関数に渡すことで最初に定義した関数の方を優先して呼び出されるようになる
twice(0, 42);        // 84
twice(0, 3.14f);     // 6.28
twice(0, "homu");    // "homu"

http://melpon.org/wandbox/permlink/WeEUFMV8MGEZRkyb

これは twice(0, 42) の場合は twice(bool, T) よりも twice(int, T) にマッチし、twice(0, "homu") の場合は SFINAE によって twice(int, T)オーバーロード候補から外れ、0bool 型に暗黙の型変換されるので twice(bool, T) にマッチするためです。
まあ、要するに

void func(int);
void func(bool);

// func(int) を呼び出す
func(0);

ということを利用しているだけですね。

[ちょっと改良]

上記のままだと毎回 0 を渡す必要があるのでラッパ関数を用意すると使い勝手がよくなります。

template<typename T>
auto
twice_impl(int, T a)
->decltype(a + a){
    return a + a;
}

template<typename T>
auto
twice_impl(bool, T t){
    return t;
}

template<typename T>
auto
twice(T t){
    // ラッパ関数を用意して、その中で twice の実装を呼び出す
    return twice_impl(0, t);
}

twice(42);        // 84
twice(3.14f);     // 6.28
twice("homu");    // "homu"

http://melpon.org/wandbox/permlink/lwTkvXx39ptEP6TN

これでユーザコードからこのハックを隠蔽する事が出来ました。

[まとめ]

と、言う感じで簡単なハックを紹介してみました。
SFINAE を使った関数を定義しているとこういう『オーバーロードが解決できない関数』が結構出てくるんで地味に重宝します。
もうちょっと汎用的にしたコードが下記の記事に載ってるので気になる方は読んでみるとよいです。

[参照]

【初心者C++er Advent Calendar 2016 19日目】C++ 楽しいメタプログラミングしよう

C++

初心者C++er Advent Calendar 2016 19日目の記事です。
クリスマスから少し過ぎてしまいましたが、最後に楽しくメタプログラミングしてみる記事でも。
初心者C++er Advent Calendar 2016 が全部うまるといいですね。

[メタプログラミングとは]

メタプログラミングの定義は言語によって異なりますが、C++ においては『テンプレート』を利用して『コンパイル時』に処理を行うことを指す事が一般的です。
今回はテンプレートを利用して『型に対して』コンパイル時に処理を行うコードを書いていきたいと思います。

[前提の知識]

[テンプレートの特殊化]

前提として『テンプレートの特殊化』を理解している必要があります。
『テンプレートの特殊化』というのはクラステンプレートなどに対して『特定の型』に対して処理を別に記述する記法です。

template<typename T>
struct X{
    
};

// 任意のテンプレート引数型に対して拡張する
// テンプレート引数が int の場合はこっちが使用される
template<>
struct X<int>{
    
};

メタプログラミングはこの特殊化を利用することが多いです。

[static_assert()]

static_assert() は『コンパイル時に意図的にエラーにするため』の機能です。

static_assert(定数式, 文字列リテラル);

のような形式で記述し、定数式false の場合に 文字列リテラル を出力してコンパイルエラーになります。

static_assert(3 > 1, "Error Message");    // OK コンパイルエラーにならない
static_assert(1 > 3, "Error Message");    // NG コンパイルエラーになる

メタプログラミングstatic_assert() を使用してテストする事が多いです。

参照:コンパイル時アサート - cpprefjp C++日本語リファレンス

[std::true_typestd::false_type]

今回つくるメタプログラミングstd::true_typestd::false_type を使用します。
この2つの型は単純に ::valuetruefalse を返すだけのクラスになります。

std::true_type::value;    // true
std::false_type::value;   // false

この2つは標準ライブラリの <type_traits> で 具体的な利用方法はこのあと書いていきます。

[メタ関数]

メタプログラミングはクラステンプレートに型を渡して処理を行ないます。

// hoge クラステンプレートに型を渡してその結果を ::type で取得する
hoge<int, float>::type

このように任意の型をクラステンプレートに渡して、その結果を ::type という型で受け取ります。
また、このように型に対して処理するクラステンプレートのことを『メタ関数』を呼びます。
ちなみにメタ関数には型以外にも『値』を返すこともあります。
その場合は

foo<int, float>::value

というふうに ::value で返すことが一般的です。
このようにメタ関数を使用、定義する場合はそのメタ関数が『型を返す』のか『値を返す』のかを意識する必要があります。

[型を比較する]

さて、まずは簡単に型を比較してみましょう。
動作イメージは以下のような感じです。

template<typename T, typename U>
struct is_same;

is_same<int, int>::value;         // true
is_same<int, float>::value;       // false
is_same<int, const int>::value;   // false

このようにある型とある型が同じ型かどうかを比較するクラステンプレートを書いてみます。

#include <type_traits>

// 元となるクラステンプレート
// デフォルトでは std::false_type を継承する
template<typename T, typename U>
struct is_same : std::false_type{};

template<typename T>
// 2つのテンプレート引数が同じ型の場合の特殊化
// 同じ型の場合は std::true_type を継承する
struct is_same<T, T> : std::true_type{};


// ::value は std::true_type と std::false_type で定義されてる
static_assert(is_same<int, int>::value       == true, "");
static_assert(is_same<int, float>::value     == false, "");
static_assert(is_same<int, const int>::value == false, "");

http://melpon.org/wandbox/permlink/VNE2apvsZ2Hq21Le

こんな感じでテンプレートの特殊化を利用して実装します。
今回は比較した結果を『値』で返すので ::value で結果を受け取ります。
ポイントとしては真になる場合は std::true_type を継承し、偽になる場合は std::false_type を継承してる点です。
これにより ::value を定義することなく継承するだけで『真』や『偽』となるクラスが定義できます。

[型に const などを付属させる]

任意の型に対して const やポインタなどを付属させるメタ関数を定義してみます。
まずはポインタを追加するメタ関数です。

template<typename T>
struct add_const{
    using type = T const;
};

static_assert(is_same<add_const<int>::type, int const>::value, "");
static_assert(is_same<add_const<int*>::type,  int* const>::value, "");
static_assert(is_same<add_const<int const>::type, int const>::value, "");


template<typename T>
struct add_pointer{
    using type = T*;
};

static_assert(is_same<add_pointer<int>::type,    int*>::value, "");
static_assert(is_same<add_pointer<void>::type,   void*>::value, "");
static_assert(is_same<add_pointer<float*>::type, float**>::value, "");

// 組み合わせて使用したり
static_assert(is_same<add_pointer<add_const<int>::type>::type, int const*>::value, "");

http://melpon.org/wandbox/permlink/GtOPyLNLgxyJK2jX

今回は結果が『値』ではなくて『型』なのでテンプレート型に対して const* を付属した type 型を定義しています。

[const やポインタを削除する]

では逆に const やポインタを削除するメタ関数を定義してみましょう。

template<typename T>
struct remove_pointer{
    using type = T;
};

template<typename T>
struct remove_pointer<T*>{
    using type = T;
};

static_assert(is_same<remove_pointer<int*>::type,  int>::value, "");
static_assert(is_same<remove_pointer<int**>::type, int*>::value, "");
static_assert(is_same<remove_pointer<int>::type,   int>::value, "");


template<typename T>
struct remove_const{
    using type = T;
};

template<typename T>
struct remove_const<T const>{
    using type = T;
};

static_assert(is_same<remove_const<int const>::type,  int>::value, "");
static_assert(is_same<remove_const<int* const>::type, int*>::value, "");
static_assert(is_same<remove_const<int>::type,        int>::value, "");

http://melpon.org/wandbox/permlink/dmYesP6x8QV9GZOK

先ほどのコードよりもちょっと複雑ですね。
const やポインタの判定に特殊化を利用しています。

template<typename T>
struct X<T*>{};

というように記述することで『ポインタ型の場合のみ』という特殊化を行うことが出来ます。

[コンパイルFizzBuzz]

さて、最後にコンパイル時に FizzBuzz を行うメタ関数を書いてみましょう

[結果の型]

今回、FizzBuzz の結果は型として返します。
ですので最初にそれぞれの結果を型として定義しておきます。

struct Fizz{};

struct Buzz{};

struct FizzBuzz{};

// Fizz, Buzz, FizzBuzz 以外の場合はこの型をラップして返す
template<int N>
struct int_{};

数値の場合は『値』ではなくて『型』を返す必要があるので、それ用に『数値を受け取るクラステンプレート』も用意しておきます。

[if_else]

分岐処理を行うために if 文を模倣するメタ関数も用意しておきます。

template<bool Cond, typename Then, typename Else>
struct if_{
    using type = Then;
};

template<typename Then, typename Else>
struct if_<false, Then, Else>{
    using type = Else;
};

static_assert(is_same<if_<true,  int, float>::type, int>::value, "");
static_assert(is_same<if_<false, int, float>::type, float>::value, "");

これも特殊化を利用してデフォルトは Then を返し、Condfalse の場合は Else を返すようにします。

[完成品]

先ほど書いたメタ関数を利用して FizzBuzz を実装すると以下のような感じになります。

struct Fizz{};

struct Buzz{};

struct FizzBuzz{};

template<int N>
struct int_{};


template<bool Cond, typename Then, typename Else>
struct if_{
    using type = Then;
};

template<typename Then, typename Else>
struct if_<false, Then, Else>{
    using type = Else;
};

static_assert(is_same<if_<true,  int, float>::type, int>::value, "");
static_assert(is_same<if_<false, int, float>::type, float>::value, "");


template<int N>
struct fizzbuzz{
    using type
        = typename if_<N % 15 == 0, FizzBuzz,
          typename if_<N %  3 == 0, Fizz,
          typename if_<N %  5 == 0, Buzz,
          int_<N> >::type >::type >::type;

//  どっちが見やすいかな…
//     using type = typename if_<
//         N % 15 == 0,
//         FizzBuzz,
//         typename if_<
//             N % 3 == 0,
//             Fizz,
//             typename if_<
//                 N % 5 == 0,
//                 Buzz,
//                 int_<N>
//             >::type
//         >::type
//     >::type;
};

static_assert(is_same<fizzbuzz<1>::type,  int_<1>>::value, "");
static_assert(is_same<fizzbuzz<2>::type,  int_<2>>::value, "");
static_assert(is_same<fizzbuzz<3>::type,  Fizz>::value, "");
static_assert(is_same<fizzbuzz<4>::type,  int_<4>>::value, "");
static_assert(is_same<fizzbuzz<5>::type,  Buzz>::value, "");
static_assert(is_same<fizzbuzz<6>::type,  Fizz>::value, "");
static_assert(is_same<fizzbuzz<10>::type, Buzz>::value, "");
static_assert(is_same<fizzbuzz<15>::type, FizzBuzz>::value, "");
static_assert(is_same<fizzbuzz<30>::type, FizzBuzz>::value, "");

http://melpon.org/wandbox/permlink/pEnLxRi5MXxKJ83C

メタプログラミングではネストした if 文を記述するのが難読化してしまうのでちょっと見づらいですが、やってること自体は一般的な FizzBuzz と同じなのでそんなに複雑ではないです。

[まとめ]

と、言う感じで簡単にメタプログラミングについて書いてみました。
C++メタプログラミングというと敷居が高いイメージがありますが、細かいところ見ていくとそんなに複雑なことをしていないことがわかると思います。
メタプログラミングC++ の醍醐味でもありますしね。
今回の記事でメタプログラミングに興味を持った方は標準ライブラリの <type_traits> を調べてみたり Boost.MPL なんかを見てみるとよいと思います。
ちなみに今回は C++03 を少し意識して書いたんですが、C++11/14 では少しメタプログラミングが記述しやすくなっているので、それについても調べてみても面白いです。
それではよい C++ を〜。

【初心者C++er Advent Calendar 2016 17日目】C++ で型と値を理解する

C++

初心者C++er Advent Calendar 2016 17日目の記事です。
初心者ネタに型と値の話でも。

[値とは]

C++ における値とは数値や文字列、クラスのインスタンスなどを指します。

// 数値や小数
42
3.14f

// 文字や文字列
'c'
"homu"

// クラスのインスタンス
std::string{}
std::vector<int>{}

まあ要はデータそのものですね。

[型とは]

一方、C++ における型とは intcharstd::string などを指します。
型は一般的に『何かの値を受け取るものを定義する時』つまり『変数を定義する時』や『関数の引数を定義する時』などに使用します。

// 42 という『値』を受け取る変数を『int 型』として定義する
int n = 42;

// 『int 型』と『float 型』を受け取って『bool 型』を返す関数を宣言する
bool func(int, float);

// 関数に『値』を渡して『値』を受け取る
bool b = func(42, 3.14f);

このように型とは『値と対になるような存在』になりどんな値にも『型』は存在します。

[型による制約]

基本的には『同じ型』の変数に対してのみ値を代入することが出来ます。

int a;      // int 型の変数を定義
a = -10;   // int 型の値を代入する
a = "homu";  // Error: 他の型を代入することは出来ない

このようにすることで任意の変数は『定義した時の型』である事が保証されます。
ただし、値によっては『別の型』を代入する事ができる場合もあります。

float b;    // float 型の変数を定義
b = 3.14f; // float 型の値を代入する
b = 42;        // OK: int 型の値を代入してもエラーにならない

このように他の型へ変換されることを暗黙の型変換と呼びます。
C++ ではしばしばこのように暗黙的に別の型へと変換される事があるので注意してください。
このように C++ では『値』がどんな『型』になるのか、また『値を演算した結果(関数を呼び出した結果)』どんな『型に変換されるのか』を意識して書くことが重要になってきます。
これに慣れてくると

result = 42 + 3.14f;

というような処理が

float = int + float;

のように型に脳内変換されます。

[値から型を定義する]

さて、ちょっと型の話をしていきましょう。
コードを書いてると値や式から型を取得したいと思うことが度々あります。

int a = 10;
float b = 3.14f;
// どんな型を返すんだっけ???
??? c = a + b;

そういう時は C++11 から追加された decltype() という機能を使用します。
decltype() は式(値)を渡すことで『その式(値)の型』を返します。

int a = 42;
decltype(a);          // int 型を返す
decltype(a) b = 10;   // 型の変わりに使用することができる

// typedef などで使用することもできる
typedef decltype(a) a_type;

float f = 3.14f;
decltype(a + f) result = a + f;   // decltype() には式も渡せる

decltype(func(a, b)) result2 = func(a, b);   // 関数も渡すことができる

int const n = 42;

// const 修飾子も加味した型を返す
decltype(n);      // const int

こんな感じです。
基本的に『decltype() を定義したスコープから見える変数』はすべて参照する事が出来ます。
ですので以下のようにC++11 で追加された『後置戻り値型』でも decltype() が使用できます。

template<typename T, typename U>
auto
plus(T a, U b)
// 型情報だけでは演算結果の型を取得するのは難しいが
// decltype() を利用すれば式から型を推論することができる
->decltype(a + b){
    return a + b;
}

decltype() が具体的にどのような型を返すのかは以下のサイトがわかりやすく解説しています。

本の虫: decltypeの訓練

[まとめ]

そんな感じで値と型の話について書いてみました。
C++(に限らないけど)において『型情報』というのは重要なファクターになります。
特に昨今のテンプレートや auto などを使用したコードでは『表面の型』が見えないことが多いです。
そういう時こそ『型』を意識してコードを書いていくことが C++ を学ぶ上で大切になっていきます。
動的型付け言語を普段書いている人にとって『型情報』というのはめんどくさい要因の一つだと思います。
しかし、『型による制約』によるリターンもとても大きいものではあります。
C++ 初心者の人はコードを書いてる時に少しだけ型について意識して書いてみると C++ への理解がより深まると思います。

【初心者C++er Advent Calendar 2016 11日目】C++ で型によるコンパイル時条件分岐技法まとめ

C++

初心者C++er Advent Calendar 2016 11日目の記事です。
空いてるようなのでまた書きました。
さて、初心者用ということで型を使用した『コンパイル時条件分岐技法』を簡単に紹介してみます。

[注意]

本記事では C++11 で動作することを想定してるコードになります。

[多重定義]

まずは一番よく利用されている多重定義ですね。
C++ では引数の型を変えることで複数の関数を定義することが出来ます。

void
print(int n){
    std::printf("int 型:%d\n", n);
}

void
print(float f){
    std::printf("float 型:%f\n", f);
}

void
print(char const* str){
    std::printf("char const* 型:%s\n", str);
}

print(42);
print(3.14f);
print("homu");

/*
output:
int 型:42
float 型:3.140000
char const* 型:homu
*/

また、型以外に引数の数によっても複数の関数を定義することが出来ます。

int
func(int n){
    return n + n;
}

int
func(int n, int m){
    return n + m;
}

func(42);     // 84
func(4, 6);   // 10

まあ流石に C++ を書いてる人で多重定義を書いたことがない人はいないと思います。

[テンプレートの特殊化]

さて、次はテンプレートを使った条件分岐です。
いわゆる『テンプレートの特殊化』と言われるやつですね。
例えば、次のようなクラステンプレートがあったとします。

template<typename T>
struct X{
    int
    twice(){
        return value + value;
    }

    T value;
};

X<int> x{42};
x.twice();       // 84

この時にテンプレートが『任意の型』の場合に twice() の実装を変えたいとします。
例えば、T = char の場合は std::string を返すようにしたい場合は X<char> に対応するクラスを新しく定義します。

template<typename T>
struct X{
    int
    twice(){
        return value + value;
    }

    T value;
};

X<int> x{42};
x.twice();       // 84


// 新しくクラスを定義する
// この template 引数は省略し
template<>
// ここに『任意の方』を記述する
struct X<char>{
    std::string
    twice(){
        return std::string{value} + value;
    }

    // ここも T 型から char 型に書き直す
    char value;
};

X<char> x2{'c'};
x2.twice();         // "cc"

こんな感じです。
まあこれは『こういうものだ!』という風に覚えるしかないですね。
また、『一部のテンプレート型に対して』も適用することが出来ます。

template<typename T, typename U>
struct X{
    
};

template<typename U>
// クラステンプレートの第一引数が int の場合のクラス
struct X<int, U>{
    
};

ここで注意するのは template<> の引数と X<int, U> の引数ですね。
template<> の引数は任意で構いませんが、X に渡す引数の数は最初に定義したクラステンプレートと同じでなければなりません。
なので、例えば、次のように拡張することは出来ません。

template<typename T>
// 引数の数が違うのでエラー
struct X<int>{
    
};

こんな感じですね。
まあ使う機会は少ないかもしれませんが、覚えておくといざという時に利用できるかもしれません。
ちなみに標準ライブラリの悪名高い std::vector<bool> もこのテンプレートの特殊化を利用してるクラスのひとつですね。

[SFINAE]

さて、SFINAE です。

「SFINAE (Substitution Failure Is Not An Errorの略称、スフィネェと読む)」は、テンプレートの置き換えに失敗した際に、即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。

参照:任意の式によるSFINAE - cpprefjp C++日本語リファレンス

まあ、これ自体は難しい仕組みではなくて、例えば次のような関数テンプレートがあったとします。

template<typename T>
typename T::value_type
twice(T){
    return value + value;
}

これは、T 型に対して『::value_type を要求してる』という関数になります。
なので、

struct Int{
    using value_type = int;
    int value;
};

// Int 型は ::value_type があるので問題なく動作する
twice(Int{42});

というコードは問題ありませんが

struct Float{
    float value;
};

// Float は ::value_type を持っていないのでエラー
twice(Float{42});

// int は ::value_type を持っていないのでエラー
twice(42);

というような引数は ::value_type がないのでエラーになります。
ここまではなんとなくわかると思います。
では次に『int 型を受け取る関数』を追加します。

template<typename T>
typename T::value_type
twice(T t){
    return t.value + t.value;
}


// 新しく関数を追加
int
twice(int n){
    return n + n;
}


struct Int{
    using value_type = int;
    int value;
};

struct Float{
    float value;
};


// OK: T::value_type twice(T) が呼ばれる
twice(Int{42});     // 84

// OK: int twice(int) が呼ばれる
twice(42);          // 84

// Error: どっちの関数にもマッチしない
twice(Float{3.14f});

この時に int 型を引数に twice() を呼び出した場合に関数テンプレートで『::value_type がない』とエラーにならずに twice(int) が呼び出されます。
つまり

即時にコンパイルエラーとはせず、置き換えに失敗した関数をオーバーロード解決の候補から除外するという言語機能である。

と言うとおり『エラーにならないでオーバーロードから除外されるだけ』という挙動になります。
SFINAE はよく enable_if などと組み合わせて利用されるためちょっとコードが複雑になってしまいますが、SFINAE 自体の仕組みはこれだけです。
そんなに難しくはないでしょ? また、上記の例では『戻り値型に T::value_type』を定義していましたが、他にも

template<
    typename T,
    // テンプレートのデフォルト引数として定義
    typename Result = typename T::value_type
>
Result
twice(T t){
    return t.value + t.value;
}

このように『テンプレートのデフォルト引数』として定義してみたり、

template<typename T>
void
print(T t, decltype(t.value)* = 0 /* メンバ変数 value にアクセスする */){
    std::cout << t.value << std::endl;
}

これならば『T がメンバ変数 value を持ってる』みたいな条件付けをすることも出来ます。
これにより『単純な型』だけではなく『より複雑な条件』で多重定義を行うことができるようになります。
このようなテクニックはいくかありますが、詳しくは"任意の式によるSFINAE"を参照してみてください。

[まとめ]

と、言う感じで簡単にコンパイル時条件分岐技法をまとめてみました。
正直 SFINAE に関してはぜんぜん紹介しきれてないですが、まあ初心者向けということで『こういうのもあるんだよー』ぐらいに思っておけばいいと思います。
SFINAE に関しては余裕があればまた別の Advent Calendar で記述したいと思います。