4

私は最近、名前付きパラメーターのイディオムが役立つ状況にかなりの数の状況に遭遇しましたが、コンパイル時に保証されることを望みます。チェーン内の参照を返す標準的な方法は、ほとんどの場合、ランタイム コンストラクター (Clang 3.3 -O3 でコンパイル) を呼び出すように見えます。

これを参照して何かを見つけることができなかったので、これを機能させようとしてconstexpr機能的なものを手に入れました:

class Foo
{
private:
    int _a;
    int _b;
public:
    constexpr Foo()
        : _a(0), _b(0)
    {}
    constexpr Foo(int a, int b)
        : _a(a), _b(b)
    {}
    constexpr Foo(const Foo & other)
        : _a(other._a), _b(other._b)
    {}
    constexpr Foo SetA(const int a) { return Foo(a, _b); }
    constexpr Foo SetB(const int b) { return Foo(_a, b); }
};
...
Foo someInstance = Foo().SetB(5).SetA(2); //works

これは少数のパラメーターでは問題ありませんが、数が多い場合はすぐに混乱します。

    //Unlike Foo, Bar takes 4 parameters...
    constexpr Bar SetA(const int a) { return Bar(a, _b, _c, _d); }
    constexpr Bar SetB(const int b) { return Bar(_a, b, _c, _d); }
    constexpr Bar SetC(const int c) { return Bar(_a, _b, c, _d); }
    constexpr Bar SetD(const int d) { return Bar(_a, _b, _c, d); }

より良い方法はありますか?多くの(30以上)パラメータを持つクラスでこれを行うことを検討していますが、将来拡張するとエラーが発生しやすくなるようです。

編集: C++1y タグを削除しました -- C++1y は問題を修正しているように見えますが (TemplateRex に感謝します!)、これは製品コード用であり、C++11 で立ち往生しています。それが無理なら、その通りだと思います。

EDIT2:これを探している理由を示すために、ユースケースを示します。現在、私たちのプラットフォームでは、開発者はハードウェア構成のビット ベクトルを明示的に設定する必要があります。これは問題ありませんが、非常にエラーが発生しやすくなっています。C99 拡張機能の指定された初期化子を使用しているものもありますが、これは問題ありませんが、標準ではありません。

HardwareConfiguration hardwareConfig = {
    .portA = HardwareConfiguration::Default,
    .portB = 0x55,
    ...
};

ただし、ほとんどはこれを使用しておらず、数字の塊を入力しているだけです。したがって、実際の改善として、次のようなものに移行したいと思います (より良いコードを強制するため):

HardwareConfiguration hardwareConfig = HardwareConfiguration()
    .SetPortA( Port().SetPolarity(Polarity::ActiveHigh) )
    .SetPortB( Port().SetPolarity(Polarity::ActiveLow) );

これははるかに冗長かもしれませんが、後で読むとはるかに明確になります。

4

2 に答える 2

4

テンプレート メタプログラミングの使用

あなたの問題を(少なくとも部分的に)解決するために私が思いついたものがあります。テンプレート メタプログラミングを使用すると、コンパイラを活用してほとんどの作業を行うことができます。これらの手法は、そのようなコードを見たことがない人には奇妙に見えますが、ありがたいことに、複雑さのほとんどはヘッダーに隠されているため、ユーザーはライブラリをきちんと簡潔に操作するだけです。

クラス定義のサンプルとその使用

以下は、クラスの定義があなたの側で何を伴うかの例です:

template <
    //Declare your fields here, with types and default values
    typename PortNumber = field<int, 100>, 
    typename PortLetter = field<char, 'A'>
>
struct MyStruct : public const_obj<MyStruct, PortNumber, PortLetter>  //Derive from const_obj like this, passing the name of your class + all field names as parameters
{
    //Your setters have to be declared like this, by calling the Set<> template provided by the base class
    //The compiler needs to be told that Set is part of MyStruct, probably because const_obj has not been instantiated at this point
    //in the parsing so it doesn't know what members it has. The result is that you have to use the weird 'typename MyStruct::template Set<>' syntax
    //You need to provide the 0-based index of the field that holds the corresponding value
    template<int portNumber>
    using SetPortNumber = typename MyStruct::template Set<0, portNumber>;

    template<int portLetter>
    using SetPortLetter = typename MyStruct::template Set<1, portLetter>;

    template<int portNumber, char portLetter>
    using SetPort = typename MyStruct::template Set<0, portNumber>
                           ::MyStruct::template Set<1, portLetter>;


    //You getters, if you want them, can be declared like this
    constexpr int GetPortNumber() const
    {
        return MyStruct::template Get<0>();
    }

    constexpr char GetPortLetter() const
    {
        return MyStruct::template Get<1>();
    }
};

クラスの使用

int main()
{
    //Compile-time generation of the type
    constexpr auto myObject = 
        MyStruct<>
        ::SetPortNumber<150>
        ::SetPortLetter<'Z'>();

    cout << myObject.GetPortNumber() << endl;
    cout << myObject.GetPortLetter() << endl;
}

ほとんどの作業はconst_objテンプレートによって行われます。コンパイル時にオブジェクトを変更するメカニズムを提供します。a と同様Tupleに、フィールドは 0 ベースのインデックスでアクセスされますが、上記の SetPortNumber および SetPortLetter で行われるように、セッターをフレンドリ名でラップすることを止めるものではありません。(Set<0> と Set<1> に転送するだけです)

ストレージについて

現在の実装では、すべてのセッターが呼び出されてオブジェクトが宣言された後、フィールドは基本クラスで名前がconst unsigned char付けられた のコンパクトな配列に格納されます。dataunsigned char ではないフィールドを使用する場合 (たとえば、上記の PortNumber で行ったように)、フィールドは大きな endien に分割されunsigned charます (必要に応じて小さな endien に変更できます)。実際のメモリアドレスを持つ実際のストレージが必要ない場合は、を変更して完全に省略できpacked_storage(以下の完全な実装リンクを参照)、値はコンパイル時に引き続きアクセスできます。

制限事項

この実装では、整数型のみをフィールドとして使用できます (short、int、long、bool、char のすべてのフレーバー)。ただし、複数のフィールドで動作するセッターを提供することはできます。例:

template<int portNumber, char portLetter>
using SetPort = typename MyStruct::template Set<0, portNumber>::
                         MyStruct::template Set<1, portLetter>;

完全なコード

この小さなライブラリを実装するための完全なコードは、次の場所にあります。

完全実装

その他の注意事項

このコードはテスト済みで、g++ と clang の両方の C++11 実装で動作します。何時間も何時間もテストされていないので、もちろんバグがあるかもしれませんが、始めるための良いベースを提供するはずです. これが役立つことを願っています!

于 2013-10-02T03:01:04.143 に答える