2017-12-31 211 views
8

は、私は、この構造体を持っていると言います私はシンプルですのstd :: make_uniqueの(と据え付ける、emplace_backの)initializer_list引数の厄介な控除

auto bla = std::make_unique<positioned>({1,2}); 

は動作するのですか?

現在のところ、コンパイラはinitializer_list<int>を照合しようとし、positionedにコンストラクタが1つしかないため、make_uniqueという配列のバリエーションを呼び出します。これは愚かです。同じ問題がemplaceemplace_back関数で発生します。可変のテンプレート引数をクラスのコンストラクタに転送する関数は、この動作を示すようです。

は、私は2 int引数のコンストラクタをpositionedを与え、明示的position{1,2}としてmake_uniqueに引数の型を指定するmake_unique、または

  • への呼び出しで {}をドロップ

    1. ことで、これを解決することができます理解しています。

    私は(make_unique実装では少しの努力を払って)見えるように、どちらもあまりにも冗長であるように見えますが、これは引数タイプの過度の指定なしに解決できます。

    これはmake_unique実装の解決可能な欠陥ですか、これは解決できない、面白くないエッジケースなので、誰も気にする必要はありませんか?

    +0

    OKです。削除しましょう。 –

    答えて

    0

    {1, 2}のようなイニシャライザーは推測できません:以下のコードは機能しません(理由は:{1, 2}のタイプは何ですか?)。

    template<typename U> 
    void foo(U args) { } 
    
    foo({1, 2}); 
    

    make_uniqueは、このテーマではちょっと複雑なバリエーションです。

    根本的な原因は、ここでは詳細に説明されていますinitializer_list and template type deduction

    +1

    私は問題が何であるかを知っています。周りに道があるかどうか疑問に思っています。 (私が望む)、ブレースされたinitリストをコンストラクタの引数型に合わせるように強制することで、newとmake_uniqueを呼び出す間にこの非対称性をなくすことが可能であると言えます。 – rubenvb

    +0

    私が見る唯一の方法は、一般的な場合にstd :: make_uniqueに転送する独自の 'make_unique'関数を作成し、必要な型を特殊化することです。 (またはあなたはubで遊んで、std :: make_uniqueを直接専門にすることができます...) –

    +0

    私は標準化されたものが許可されたと思ったのですか? – rubenvb

    1

    は、私の知る限り見ることができるように、これを行うための最も現実的な方法は、中括弧を取り除くために、おそらくあり、かつ離散的に引数を取るコンストラクタを追加します。

    struct position 
    { 
        int x, y; 
    
        position(int x, int y) : x(x), y(y) {} 
    }; 
    
    class positioned 
    { 
    public: 
        positioned(int x, int y) : pos(x, y) {} 
    private: 
        position pos; 
    }; 
    
    int main() { 
        auto bla = std::make_unique<positioned>(1,2); 
    } 
    

    positionが複数のctorのを持っていた場合、あなたはおそらく、いくつかの任意のパラメータを取り、positionのCTOR(複数可)にそれらを通過するpositionedための可変長引数テンプレートのctorを作成したいと思います。

    struct position 
    { 
        int x, y; 
    
        position(int x, int y) : x(x), y(y) {} 
        position(int b) : x(b), y(b) {} // useless--only to demo a different ctor 
    }; 
    
    class positioned 
    { 
    public: 
        template <class... Args> 
        positioned(Args&&... a) : pos(std::forward<Args>(a)...) {} 
    private: 
        position pos; 
    }; 
    
    int main() { 
        auto bla = std::make_unique<positioned>(1,2); // use 1st ctor 
        auto bla2 = std::make_unique<positioned>(1); // use 2nd ctor 
    } 
    

    引数がpositionpositionedmake_uniqueから転送されてますこの方法です。これは効率の潜在的なメリットを少なくともいくらか与えます - 一時オブジェクトを作成するために引数を使用するのではなく、元のオブジェクトを初期化するために渡される代わりに、元のオブジェクトを(継承した)オブジェクトのctorに直接基礎となるオブジェクトであるため、その場で一度しか構築しません。

    これは、多額の融通性があることに注意してください。

    #include <memory> 
    
    struct position2 
    { 
        int x, y; 
    
        position2(int x, int y) : x(x), y(y) {} 
    }; 
    
    struct position3 { 
        int x, y, z; 
    
        position3(int x, int y, int z) : x(x), y(y), z(z) {} 
    }; 
    
    template <class Pos> 
    class positioned 
    { 
    public: 
        template <class... Args> 
        positioned(Args&&... a) : pos(std::forward<Args>(a)...) {} 
    private: 
        Pos pos; 
    }; 
    
    int main() { 
        auto bla = std::make_unique<positioned<position2>>(1,2); 
        auto bla2 = std::make_unique<positioned<position3>>(1, 2, 3); 
    } 
    

    互換性::私はmake_uniqueは、その転送/可変引数のctorのを得たときにそれはだから、これは、C++ 14以降が必要と考えている。例えば、のpositionedは、テンプレート、および基礎となるpositionテンプレート引数そのものだったと仮定しましょう

    7

    braced-init-listが指定されていると、関数テンプレート引数の減算が機能しません。実際の式に基づいてのみ動作します。

    positionedは、とにかく{1, 2}から初期化されたリストにすることはできません。これは2つの引数コンストラクタを呼び出そうとし、positionedにはそのようなコンストラクタがありません。 positioned({1, 2})またはpositioned{{1, 2}}を使用する必要があります。

    このように、一般的な解決法は、構成しているタイプのすべての可能なコンストラクタの署名を、何とか魔法のように再現させることです。これは明らかに現時点ではC++で行うのは妥当なことではありません。

    代替は、オブジェクトを作成するためにラムダを使用することで、内部new式に戻っprvalueを適用するためにC++ 17の保証エリジオンのルールを使用し、代替make機能を記述します。

    template<typename T, typename Func, typename ...Args> 
    std::unique_ptr<T> inject_unique(Func f, Args &&...args) 
    { 
        return std::unique_ptr<T>(new auto(f(std::forward<Args>(args)...))); 
    } 
    
    auto ptr = inject_unique<positioned>([]() {return positioned({1, 2});}); 
    

    あなたをtypename Tパラメータを捨てることもできます:

    template<typename Func, typename ...Args> 
    auto inject_unique(Func f, Args &&...args) 
    { 
        using out_type = decltype(f(std::forward<Args>(args)...)); 
        return std::unique_ptr<out_type>(new auto(f(std::forward<Args>(args)...))); 
    } 
    
    auto ptr = inject_unique([]() {return positioned({1, 2});}); 
    
    +0

    疑わしい質問かもしれませんが、なぜあなたは 'make_unique'を使用していませんか? – Rakete1111

    +0

    @ Rakete1111:これはコピー/移動を呼び出すためです。このようにして、保証されたelisionで、それはしません。オブジェクトを直接構築します。 –

    +0

    私はちょうどコピー/移動が起こるべき場所を得るように見えない。 'make_unique'はまったく同じことを行い、リファレンスを渡すことでそのパラメータを受け取ります。混乱しますか?ありがとうございます:)ありがとう – Rakete1111

    関連する問題