2012-05-27

ML名古屋での発表の際に使ったスライド

Missing Language Features in C++11

C++11の黒歴史

C++11で規格入りしているが、おそらく使われることがない機能。

  • 継承コンストラクター
  • 属性

問題

既存のクラスから派生して、ごく簡単な機能を付け加えたい。

// 有名ライブラリの優れたクラス
class SuperUltraDeluxeClass
{
// 高度な実装
} ;

// 自分の書いたラッパー
class MyClass
    : public SuperUltraDeluxeClass
{
// ちょっと便利な機能の付け足し
} ;

自分のクラスは、基本クラスと同じように振舞ってほしい。

継承されないコンストラクター

しかし、コンストラクターは通常、継承されない。

class Base
{
public :
    Base() ;
    Base( int ) ;
    Base( double ) ;
} ;
class Derived : public Base 
{ } ;

Derived d( 0 ) ; // エラー

このままでは、基本クラスと同じように初期化できない。

コンストラクターの手動継承

コンストラクターは自分で書くしかない。

class Derived : public Base
{
public :
    Derived() ;
    Derived( int i ) : Base( i ) ;
    Derived( double d ) : Base( d ) ;
} ;

面倒だ!

継承コンストラクター

C++11では、この問題を解決すべく、継承コンストラクターが導入された。

class Derived
    : public Base
{
public :
    using Base::Base ; // コンストラクターを継承
} ;

Derived d( 0 ) ; // OK

簡単だ!

継承コンストラクターの詳細

継承コンストラクターは、using宣言でコンストラクターを指定することにより発動する。

struct B
{
// コンストラクターの宣言
} ;
struct D : B 
{
    // コンストラクターの継承
    using B::B ;
} ;

とてもわかりやすい機能であり、初心者でも簡単に使える。

  • 教えるのは簡単
  • 書くのも簡単
  • 使うのも簡単

以上、めでたしめでたし。

ならず。

継承コンストラクターの問題点

有名なコンパイラーはどこも実装していない。

C++11規格制定の最終段階で、実装経験のない機能を取り除こうという提案が出された。

すると各地から実装例の報告がなされ、結局、ほとんどの機能は、すでに実装されていることが明らかになった。

継承コンストラクターの実装例もあったので、除去は免れた。

なぜ誰も実装しないのか

継承コンストラクターが実装されない理由

  • Variadic Templatesでエミュレートできる
  • 実装が意外と面倒

Variadic Templatesによるエミュレート

N3258: US-65: Removing Inheriting Constructorsを参照。

template < typename BaseType >
class Derived 
    : public BaseType
{
public :
    template < typename ... Types >
    Derived( Types && ... args )
        : BaseType( std::forward<Types>(args)... )
        // 追加のメンバー初期化
    {
        // 追加の初期化処理
    }
} ;

しかも、追加でメンバー初期化や初期化処理が実行できる。

さて、読者はstd::forwardの意味がわかるだろうか?

エミュレートの問題点

  • 一般人がVariadic Templatesを使うのは難しい
    • しかも、Perfect Forwardingの知識まで要求される
  • Variadic Templatesは何にでもインスタンス化してしまう
    • インスタンス化を防ぐ方法はある。しかし・・・
  • Variadic Templatesのエラーメッセージは読みにくい。
    • エラーメッセージを読みやすくする方法はある。しかし・・・

エミュレーション問題の解決?

自動的なインスタンス化を防ぐ方法

template < typename ... Types ,
    typename Dummy = typename std::enable_if<
        std::is_constructible< BaseType, Types...>::value
    >::type 
>
Derived( Types ... args )
    : BaseType( std::forward<Types>(args)... )
{ }

読者はこのコードの意味がすぐに理解できるだろうか。ちなみに、我々C++プログラマーの世界では常識である。

エラーメッセージを読みやすくする方法

template < typename ... Types >
Derived( Types ... args )
    : BaseType( std::forward<Types>(args)... )
{
   static_assert( std::is_constructible< BaseType, Types...>::value
    , "BaseType is not constructible from given argument list." ) ;
}

これは言うほど難しくないのだが、やはりC++に不慣れな人間は、これだけでも黒魔術的コードだと錯覚するであろう。たんに標準ライブラリのis_constructibleと、コア言語機能のstatic_castを使っているだけなのだが。

発表当日では、私はこのコードの意味を忘れたふりをしてウケを狙った。あまりウケなかったようだ。

Variadic Templatesは難しい

Variadic Templatesは、一般人には使いこなせない。

だからこそ継承コンストラクターが提案された。

継承コンストラクターは使いやすい。

では、実装はどこ?

なぜ実装されないのか

継承コンストラクターの実装は困難である。

  • 規格の文面が曖昧
    • 誰も実装していなかったから問題点が洗い出されなかった
    • そもそも、using宣言の文法を流用したのは失敗だった
  • 新しい専用の呼び出し規約が必要
    • C言語から受け継いだ可変引数に対応するため

Clang開発者の声

Clang開発者の声

Douglas Gregor、「継承コンストラクターは意外と難しい。それに、誰も気にかけてない」

zygoloid、「継承コンストラクターはC++11のexportになるべきだ」
Douglas Gregor、「賛成」

Douglas Gregor、「たかが継承コンストラクターのために新しい呼び出し規約を作るのは面倒」

予想

継承コンストラクターは、C++11のexportになる。

問題

コンパイラー独自の機能を付け加えたい。

  • アライメント
  • 最適化の強制、抑制
  • 関数の呼び出し規約の指定
  • その他多数

独自機能は必要である。

現状

プリプロセッサーや独自のキーワードが用意されている。

// MSVCの独自マクロ
#pragma ...

// MSVCの独自キーワード
__declspec( ... )

// GCCの独自キーワード
__attribute__( ... )

どのコンパイラーも、てんでばらばらに実装している。

既存の独自機能の多くは、単純なプリプロセッサーやキーワードで指定する、ちょっとしたアノテーションである。

機能の指定方法を統一できないものか。

理想的な解決方法

C++11では、汎用的な機能の指定方法の文法として、属性が導入された。

[[ ... ]]

これにて文法が統一されるはず。

無問題

属性には問題がない。

  • 規格制定前に試験的に実装されたことがある
  • 既存の文法との衝突は少ない(配列の添字の中のlambdaの問題は解決された)
  • 既存の独自文法を置き換える柔軟な配置場所(ほぼ、どこにでも配置できる)

属性の現実

  • 既存のコードは自動的に書き換わらない
    • コンパイラーは、既存の文法も残しておかなければならない
    • 移植性のない独自機能のために新しい文法を学びたいか?
    • 移植性のないコードをほんの少し規格準拠に書きたいか?
    • 誰も既存の完璧に動くコードを書き換えたくはない
  • どうせ環境依存の独自機能なのだから、文法に互換性があっても意味がない

4 comments:

惑星 said...

おお、これは面白いですね。自分の住んでいるところの近く開催されていたら聞きに行きたかったです。

ところで継承コンストラクターの問題点で、有名なコンパイラーはどこも実装していないとありますが、その一方で継承コンストラクターの実装例もあったと書いてあります。

これは有名ではないコンパイラーのいくつかが実装していた、ということでしょうか。

江添亮 said...

「いくつか」ではありません。
唯一実装していたコンパイラーがみつかったのです。
The lonely compiler: Интерстрон: реализация нового стандарта Си++

ただし、このコンパイラーは非公開のハードウェア用のコンパイラーです。それ以上の詳細は全くわかりません。

オンラインで試すこともできます。

Interstron

江添亮 said...

今、C++標準化委員会のMLを公開しようという動きが進んでいるので、将来MLを検索して、このへんの経緯も学べるようになるでしょう。

惑星 said...

唯一ですか。非公開のハードウェア用コンパイラということは相当ユーザー数が少なく、あまり試されていないかと思います。これを実装経験がある、と考えるのはあまり適当でないように思えますが……。
(もちろん江副さんに文句を言っているわけではないです)

それにしてもC++11は、ユーザー定義リテラルなどのように、機能を詰め込みすぎたように感じます。あまり言語仕様を複雑にしない方がいいと思うのですが。