LoginSignup
7
6

More than 5 years have passed since last update.

[C++11] 簡易版strong typedef, opaque alias

Last updated at Posted at 2015-03-19

概要

typedefでクラスにエイリアスを付けられる。

typedef int foo;

usingを用いたエイリアス宣言もある。
typedefでは分かり辛い関数型等もusingを使うと人間に優しい。

using bar = int;

異なるtypedefエイリアス名は型システム上は同じ型だと認識される。

using foo = int;
using bar = int;
static_assert(std::is_same<foo, bar>::value, "同じ型だよ");

数値型に、コンテクストに合わせてエイリアス名を付けるのは良く有る。
このイディオムはユーザーにとって危険な振舞いがまれに良く起きる。
例えば、異なるエイリアス名でも同じ型なので、互いに代入可能。

using inch_type = int;
using mm_type = int;

inch_type l;
mm_type h;
l = h; // あっ

ドキュメントに頼ってこれらを管理するのは大変危険。
エイリアスで異なる型を導入してコンパイラに検知させたい
==> strong typedef, opaque alias

実装

超簡単

#include <type_traits>
namespace internal {
template <typename T, typename Tag, bool IsClass>
struct opaque_alias_impl : T
{
    private:
    using self = opaque_alias<T, Tag, true>;
    public:
    using prototype = T;
    using T::T // derive ctor

    opaque_alias_impl () = default;
    opaque_alias_impl (self const &) = default;
    opaque_alias_impl (self &&) = default;

    self & operator = (self const &) = default;
    self & operator = (self &&) = default;

    opaque_alias (T const & value) : T(value) {}
    opaque_alias (T && value) : T(std::move(value)) {}

    T & cast () { return *static_cast<T *>(this); }
    T const & cast () const { return *static_cast<T const *>(this); }
};

template <typename T, typename Tag>
struct opaque_alias_impl<T, Tag, false>
{
    private:
    using self = opaque_alias<T, Tag, false>;
    T value;
    public:
    using prototype = T;

    opaque_alias_impl () = default;
    opaque_alias_impl (self const &) = default;
    opaque_alias_impl (self &&) = default;

    self & operator = (self const &) = default;
    self & operator = (self &&) = default;

    opaque_alias (T const & value) : value(value) {}
    opaque_alias (T && value) : value(std::move(value)) {}

    T & cast () { return value; }
    T const & cast () const { return value; }
};
} // internal

template <typename T, typename Tag>
using opaque_alias = internal::opaque_alias<T, Tag, std::is_class<T>::value>;

使用例

opaque_alias クラステンプレートを使うと、異なるエイリアスを付けると同時に、異なる型を導入できる。
元の型 => エイリアス型は、暗黙に変換可能。
エイリアス型 => 元の型は、明示的にのみ変換可能。
新たに導入された型同士は、変換不可能。


using inch_type = opaque_alias<int, class inch_tag>;
using mm_type = opaque_alias<int, class mm_tag>;

inch_type l; // OK
mm_type h(100); // OK

l = 0.5; // OK
h = 3.0; // OK
l = h; // NG
l = l.cast(); // OK

int i;
i = l // NG;
i = l.cast() // OK;

クラスのエイリアス型を作ると、、元のクラスのメンバに直接アクセスできる。


struct foo 
{
    int f () { return 1; }
};

using foo_alias_type = opaque_alias<foo, class foo_tag>;

foo_alias_type a;
a.f();

エイリアス型 => 元の型を暗黙に変換可能にすると、意図しない動作が可能になってしまう。
もし、それでも良いのなら、型変換演算子を定義すれば良い。
ちなみに、型変換演算子にexplicitを付けると、(int &)(l)等と書かないといけないため煩わしい。
参照を返さないと、元の型のコンストラクタが走る。


//implicit版
using inch_type = opaque_alias<int, class inch_tag>;
using mm_type = opaque_alias<int, class mm_tag>;

inch_type l;
mm_type h;

l = 1 + h; // h が int型に変換されて、(1 + h)の値がinch_typeに変換されてしまう。

エイリアスされた型は元の型とはもはや違うので、メタプログラミングで変な動作を起こすかも知れない。
簡易版なのでこんな物でいいでしょう。
strong typedef, opaque aliasに対して、柔軟に型変換の指定のできる文法をコア言語に追加する話があるらしいが、やりすぎだと思う。

エイリアス型=>元の型の暗黙の変換を許す場合、opaque_aliasを置いた名前空間がADLで捕捉されるので注意して下さい。

7
6
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
7
6