Edited at

非型テンプレートパラメータに浮動小数点数を渡す方法(C++2aバージョン)

最近、以前書いた

非型テンプレートパラメータに浮動小数点数を渡す方法

に通知が2個も来まして、

「非型テンプレートパラメータに浮動小数点数を渡す方法」をC++11に移植してみた

【C++】任意の非型テンプレートパラメータをもたせる

という記事が発表されていたので、元ネタ著者としては改めて最新の規格で実装することを考えてみようかな、という次第です。

さて、C++標準化委員会では規格に新しい仕様を追加するときに、Proposalと呼ばれる文書を用意し、議論を経て修正を加えていく手法を取っています。

一覧はここから見ることができまして、最新の規格を追いかけるのが大好きな人々はこれを読み漁っています(ホンマか?)

その中の1つに、 Class Types in Non-Type Template Parameters という提案がありまして、これは次期C++2aに盛り込まれることが内定しております。

これはタイトル通り、非型テンプレートパラメータにクラス型を使えるようにする提案です。

非型テンプレートパラメータの仕様はこれによって大きく変わり、 左辺値参照以外では strong structural equality という条件を満たすリテラル型の値をとることができるようになりました。

GCCでは既に試すことができるようなのでコンパイラの挙動と規格を突き合わせながら四苦八苦して解読を試みていたところ、

と教えてもらいまして、 strong structural equality の条件はこちらの解説を読んでもらえばいいと思います。

基本的にはクラス型のビット表現が一致するときに等価(equal)であることが担保できるのであれば良いようですが、浮動小数点数は0.0と-0.0が異なるビット表現でも等し(equivalent)かったり、NaNとNaNが同じビット表現でも等しくなかったりするので strong structural equality を満たすことはできません。

ただし、以前の記事でもやったように、「浮動小数点数 ←→ strong structural equality を満たす型」の変換を実装してやれば、その型を引数にすることはできます。

さて、C++2aでは他にも色々機能が追加されているのですが、その中の一つに、 std::bit_cast というものがあります。

これは、同じサイズのトリビアルにコピー可能な型同士をビット表現そのまま変換できる関数で、 double から std::uint64_t への変換( double が64bitの場合)などが簡単にできるようになります。つまり、以前の記事で必死にIEEE754の内部表現を分解してやっていたことが、この関数一発でできるようになるわけです。

しかも、以前の記事では浮動小数点数型と同一サイズの整数型を泥臭く調べていましたが、 std::array<std::byte, sizeof(double)>std::bit_cast することもできるし、 strong structural equality も満たしています。

したがって、もはや浮動小数点数型のサイズも、内部表現の仕様も気にする必要はありません。

我々はこんな型を作ればいいのです。

#include <array>

#include <bit>
#include <cstddef>

namespace alt_float {

template<typename T>
class float_t {
using inner_type = std::array<std::byte, sizeof(T)>;
alignof(T) inner_type value{};
public:
constexpr float_t() noexcept = default;
constexpr float_t(const float_t&) noexcept = default;
constexpr float_t(float_t&&) noexcept = default;
constexpr float_t(T v) noexcept: value{ std::bit_cast<inner_type>(v) } {}
constexpr float_t& operator=(const float_t&) noexcept = default;
constexpr float_t& operator=(float_t&&) noexcept = default;
constexpr float_t& operator=(T v) noexcept {
value = std::bit_cast<inner_type>(v);
return *this;
}
constexpr operator T() const noexcept { return std::bit_cast<T>(value); }
};

}

算術演算なども定義してもいいですが、そこらへんは以前の記事で書いたし割愛します。

使い方は実に簡単。浮動小数点数を受け取りたいテンプレートパラメータのところで、

template<alt_float::float_t<double> x>

class op;

などとするだけ。表記が冗長ならusingでエイリアスを定義してもいいでしょう。

動作確認に使ったGCCの実装がおかしくなければ、

template<alt_float::float_t x>

class op;

とだけ書くことも可能な模様。この場合はfloatdoublelong doubleも受け取れるようになります1

テンプレートパラメータに実引数を渡すときも明快で、

op<3.14>

などと書けば良し。浮動小数点数型から暗黙変換できるので、ユーザー定義リテラルのような小細工も必要ありません2

余談。

さて実は、std::bit_castが実装されたコンパイラがまだないので、上記のコードはコンパイルできません。

しかし、GCCはなぜか、メンバ変数に浮動小数点数を含んでいてもstrong structural equalityと判断するようです3。なので、bit_castを使用せずともそのままメンバ変数に浮動小数点数型を持つことができます。

というわけで、上記のコードとは少し異なりますが、最後にデモを貼っておきます。

https://wandbox.org/permlink/IlG1AOjqtrfzakRO





  1. ちなみに実はこのままだとintboolなんかも受け取れてしまうので、C++2aらしくconstraintを書くとより良いと思いますがこの場では割愛します。 



  2. 逆に、暗黙変換をなくしてユーザー定義リテラルを用意しても良いでしょう。 



  3. おそらくバグなのだと思います。