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

ユーザ定義リテラル(と Sprout)を利用してテンプレート引数に float 値を渡す

さて、テンプレート引数には int 型等の整数値を直接渡すことはできますが、float 値を直接渡すことはできません。

// こういう風に float 値を受け取ることはできない
template<float F> 
struct X{
    ...
};

こういう場合、std::ratio みたいに分子と分母に分けて整数値渡すのが丸い気がするんですが、あまりスマートではないですね。

ユーザ定義リテラルを利用する

ってことでユーザ定義リテラルを使ってもっとわかりやすく書けるようにしてみました。

#include <ratio>
#include <iostream>
#include <sprout/string.hpp>
#include <sprout/string/string_to_float.hpp>

template<char... Chars>
struct chars{};

template<char... Chars>
chars<Chars...>
operator "" _f();

template<typename T>
struct to_float;

template<char... Chars>
struct to_float<chars<Chars...>>{
    static constexpr float value = sprout::stof(sprout::make_string(Chars...));
};


template<typename Float>
struct twice{
    static constexpr float value = to_float<Float>::value * 2;
};


int
main(){
    static_assert(twice<decltype(3.14_f)>::value == 3.14f * 2, "");
    std::cout << twice<decltype(3.14_f)>::value << std::endl;
    return 0;
}

仕組み自体は簡単で、実数を文字列(今回は可変長テンプレート引数)として扱うことでテンプレート引数に渡しています。
例えば、3.14 という数値は chars<'3', '.', '1', '4'> というような型になります。
ただ、このままだと扱いづらいので User defined literals と decltype を使って数値から型を簡単に定義できるようにしています。
decltype(3.14_f) という風に書くと chars<'3', '.', '1', '4'> という風な型に変換されます。
これはユーザ定義リテラルが数値を char 型の可変長テンプレート引数で受け取ることができることを利用しています。
あとはその文字列を実数に変換すれば擬似的に実数をテンプレート引数へと渡す事ができます。
今回、コンパイル時に文字列から実数へ変換する処理は Sprout を利用しています。
これで割と使い勝手がよく、実数をテンプレート引数へと渡せる事ができたんじゃないでしょうか。
ちなみにこのコードは C++11 以降で動作します。