しばらく前に「整数型の大きさで関数をオーバーロードする方法」という表題で記事が投稿されました。 これは興味深い話題です。 同じ大きさで同じ符号の整数型は混同しても (C/C++ の世界では) おおよそ問題ないにもかかわらず、型機構の下では区別されてしまます。 それをあらためて同じように扱えるようにするというのは屋上屋を架すようではありますが、型機構に保護されていることとの二律背反であり、 C++ らしさを象徴しているようにも思います。
さて、元記事では長くならないようにマクロで書かれていますが、あえて C++ らしく書くとすればどうあるべきなのだろうと SFINAE の練習を兼ねて書いてみました。
conversion_rank_traits.h
// -*- mode: c++ -*-
#ifndef HEADER_1301cf13c7f0041bbbbb364e6d8094c7
#define HEADER_1301cf13c7f0041bbbbb364e6d8094c7
#include <type_traits>
template<bool> class is_true;
template<> class is_true<true> : public std::true_type {};
template<> class is_true<false> : public std::false_type {};
template<class T, class U>
using is_same_size = is_true<sizeof(T)==sizeof(U)>;
template<class T, class U>
using is_same_signed =
is_true<std::is_signed<T>::value==std::is_signed<U>::value>;
template<class T, class U>
using is_same_rank_integral =
is_true<std::is_integral<T>::value
&& std::is_integral<U>::value
&& is_same_size<T,U>::value
&& is_same_signed<T,U>::value>;
template<class T, class U, class V=void>
using enable_if_same_rank_integral =
typename std::enable_if<is_same_rank_integral<T, U>::value, V>;
#endif
以下のように使います。
sample.cpp
#include <iostream>
#include "conversion_rank_traits.h"
#ifdef __WIN32__
#include <windows.h>
#else
typedef unsigned long DWORD;
#endif
template<class T>
typename enable_if_same_rank_integral<std::uint8_t, T>::type f(T) {
std::cout << "uint8_t\n";
}
template<class T>
typename enable_if_same_rank_integral<std::uint16_t, T>::type f(T) {
std::cout << "uint16_t\n";
}
template<class T>
typename enable_if_same_rank_integral<std::uint32_t, T>::type f(T) {
std::cout << "uint32_t\n";
}
template<class T>
typename enable_if_same_rank_integral<std::uint64_t, T>::type f(T) {
std::cout << "uint64_t\n";
}
int main(void) {
DWORD x = 12345;
f(x);
return 0;
}
C++ の一般的な作法に則ろうとするとどうしても冗長にならざるを得ないようです。 冗長であるかわりに、小さい単位で名前を付けながら段階的な構築をしやすいという利点もあります。 完成形が明確にわかっている場合は一気に書いてしまって良いと思いますが、試行錯誤するにあたっては必要そうな部品を徐々に作っていった方がやりやすいように感じました。