質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.37%

conceptを使った関数でコンパイルエラーが発生する

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 871

score 170

質問内容

私はSFINAEとconceptを使い、
スマートポインタのテンプレート引数Typeに依存せずに
入力された下記オブジェクトを判別できるはずの関数
identificationを作成しました。

  • std::unique_ptr<Type>
  • std::shared_ptr<Type>
  • std::weak_ptr<Type>
  • ポインタ(動的配列)
  • 静的配列
  • その他すべて

しかしテンプレート引数Typeをint[]とするとエラーが発生してしまいます。
エラー文を読んでも何が言いたいのかよくわからないです。
どのように書けばエラーを抑えることができるでしょうか。

あとconceptは勉強し始めたばかりで慣れていませんので
あまり良い書き方でない部分があれば
教えていただければ幸いです。

ご回答どうかよろしくお願い致します。

元コード

どんどん追記していくうちに文字数制限に引っかかってしまったのでので元コードを
wandboxへ移動しました。

エラー文

[ 50%] Building CXX object CMakeFiles/a.out.dir/main.cpp.o
[ソースのパス]/main.cpp:112:20: error: call to deleted constructor of 'std::unique_ptr<int [], std::default_delete<int []> >'
    identification(heap_array);
                   ^~~~~~~~~~

/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unique_ptr.h:689:7: note: 'unique_ptr' has been explicitly marked deleted here
      unique_ptr(const unique_ptr&) = delete;
      ^

[ソースのパス]/main.cpp:76:28: note: passing argument to parameter here
auto identification(auto...)noexcept{       //  その他が入力された場合実行
                           ^

エラー内容

  • error:'std::unique_ptr<int[]、std::default_delete<int[]>>'の削除されたコンストラクターの呼び出し
    何が言いたいのか私にはわかりません。

  • note:「unique_ptr」はここで明示的に削除済みとしてマークされています
    これも何が言いたいのか私にはわかりません。

  • note: ここに引数をパラメータに渡します
    この部分はコード中のエラーの発生する部分をコメントアウト
    すれば消えるので恐らく今回の問題とは直接関係ないかもしれないです。

開発環境の備考

上記コードはすべてc++20でコンパイルするものとする。

ツールの種類 ツールの名前 バージョン
コンパイラ clang++ 10.0.0
OS Linux Mint 20.0

追記

Bearded-Ockhamさんの回答を元に修正した
問題を解決するためのコードを下記に記す。

#include <iostream>
#include <memory>
#include <vector>

///////////////////
//
//  コンセプト定義
//
///////////////////
template<typename Type> concept IsUniquePtr = requires(Type type1, Type& type2){  //  std::unique_ptrか判別
    type1 = nullptr;
    type1.release();
    type1.reset();
    type1.swap(type2);
    type1.get();
    type1.get_deleter();
    //*type1; <- ここがエラーの原因
    type1.operator bool();
};

template<typename Type> concept IsSharedPtr = requires(Type type1, Type& type2){  //  std::shared_ptrか判別
    type1 = type2;
    type1.reset();
    type1.swap(type2);
    type1.get();
    //*type1; <- はstd::shared_ptr<int[]>が入力されたときetcと表示してしまう原因となる。
    type1.use_count();
    type1.unique();
    type1.operator bool();
    type1.owner_before(type2);
};

template<typename Type> concept IsWeakPtr = requires(Type type1, Type& type2){     //  std::weak_ptrか判別
    type1 = type2;
    type1.swap(type2);
    type1.reset();
    type1.use_count();
    type1.expired();
    type1.lock();
    type1.owner_before(type2);
};

template<typename Type> concept IsPointer = std::is_pointer_v<Type>;  //  ポインタ化判別
template<typename Type> concept IsArray = std::is_array_v<Type>;      //  配列か判別

////////////////////////////////////
//
//  スマートポインタや配列の識別関数
//
////////////////////////////////////
template<IsUniquePtr Up>
auto identification(const Up&)noexcept{     //  unique_ptrが入力された場合実行
    std::cout << "unique_ptr" << std::endl;
}

template<IsSharedPtr Sp>
auto identification(const Sp&)noexcept{     //  shared_ptrが入力された場合実行
    std::cout << "shared_ptr" << std::endl;
}

template<IsWeakPtr Wp>
auto identification(const Wp&)noexcept{     //  weak_ptrが入力された場合実行
    std::cout << "weak_ptr" << std::endl;
}

template<IsPointer Pt>
auto identification(const Pt&)noexcept{     //  ポインタが入力された場合実行
    std::cout << "pointer" << std::endl;
}

template<IsArray Ar>
auto identification(const Ar&)noexcept{     //  配列が入力された場合実行
    std::cout << "array" << std::endl;
}

auto identification(auto...)noexcept{       //  その他が入力された場合実行
    std::cout << "etc" << std::endl;
}

//////////////
//
//  メイン関数
//
//////////////
int main(){
    //  スマートポインタの型には依存しない
    using UseType = float;
    //using Type = int;
    //using Type = std::vector<int>;

    //  スマートポインタや配列や動的配列確保
    auto unique_ptr_mem = std::make_unique<UseType>();
    auto shared_ptr_mem = std::make_shared<UseType>();
    auto weak_ptr_mem = std::weak_ptr<UseType>();
    auto vector_mem = std::vector<UseType>();
    auto pointer_mem = new UseType[3];
    UseType array_mem[3];

    identification(unique_ptr_mem);//  unique_ptrと表示される(正常)
    identification(shared_ptr_mem);//  shared_ptrと表示される(正常)
    identification(weak_ptr_mem);//    weak_ptrと表示される(正常)
    identification(pointer_mem);//     pointerと表示される(正常)
    identification(array_mem);//       arrayと表示される(正常)
    identification(vector_mem);//      etcと表示される(正常)

    delete[] pointer_mem;

    //
    //  ここから問題の部分
    //
    auto heap_array = std::make_unique<int[]>(5);
    identification(heap_array);//      unique_ptrと表示される!!!(正常)

    return 0;
}

追記

yohhoyさんの回答を元に修正した
問題を解決するためのコードを下記に記す。

#include <iostream>
#include <memory>
#include <vector>

////////////////////////////////////////////////////////////////////////////////////////////
//
//  コンセプト定義
//
//  備考:
//  typename スマートポインタ::element_typeでスマートポインタのテンプレート引数を参照可能
//
//  また、スマートポインタの場合テンプレート引数が配列か否かで作られるクラス形が異なるので
//  コンセプトはそれらのコンセプトの論理和でなければならない。
//
////////////////////////////////////////////////////////////////////////////////////////////

template<typename Type> concept IsUniquePtr =                               //  std::unique_ptrか判別
  std::is_same_v<Type, std::unique_ptr<typename Type::element_type>>        //  テンプレート引数が配列型以外の場合
  || std::is_same_v<Type, std::unique_ptr<typename Type::element_type[]>>;  //  テンプレート引数が配列型の場合

template<typename Type> concept IsSharedPtr =                               //  std::shared_ptrか判別
  std::is_same_v<Type, std::shared_ptr<typename Type::element_type>>        //  テンプレート引数が配列型以外の場合
  || std::is_same_v<Type, std::shared_ptr<typename Type::element_type[]>>;  //  テンプレート引数が配列型の場合

template<typename Type> concept IsWeakPtr =                                 //  std::weak_ptrか判別
  std::is_same_v<Type, std::weak_ptr<typename Type::element_type>>          //  テンプレート引数が配列型以外の場合
  || std::is_same_v<Type, std::weak_ptr<typename Type::element_type[]>>;    //  テンプレート引数が配列型の場合


template<typename Type> concept IsPointer = std::is_pointer_v<Type>;        //  ポインタか判別
template<typename Type> concept IsArray = std::is_array_v<Type>;            //  配列か判別

////////////////////////////////////
//
//  スマートポインタや配列の識別関数
//
////////////////////////////////////
inline auto identification(IsUniquePtr auto const &){     //  unique_ptrが入力された場合実行
    std::cout << "unique_ptr" << std::endl;
}

inline auto identification(IsSharedPtr auto const &){     //  shared_ptrが入力された場合実行
    std::cout << "shared_ptr" << std::endl;
}

inline auto identification(IsWeakPtr auto const &){     //  weak_ptrが入力された場合実行
    std::cout << "weak_ptr" << std::endl;
}

inline auto identification(IsPointer auto const &){     //  ポインタが入力された場合実行
    std::cout << "pointer" << std::endl;
}

inline auto identification(IsArray auto const &){     //  配列が入力された場合実行
    std::cout << "array" << std::endl;
}

inline auto identification(auto...){       //  その他が入力された場合実行
    std::cout << "etc" << std::endl;
}

//////////////
//
//  メイン関数
//
//////////////
int main(){
    //  スマートポインタの型には依存しない
    using UseType = float;
    //using Type = int;
    //using Type = std::vector<int>;

    //配列などの定数
    constexpr size_t size = 5;

    //  スマートポインタや配列や動的配列確保
    auto unique_ptr_mem = std::make_unique<UseType>();
    auto shared_ptr_mem = std::make_shared<UseType>();
    auto weak_ptr_mem = std::weak_ptr<UseType>();
    auto vector_mem = std::vector<UseType>();
    auto pointer_mem = new UseType[size];
    UseType array_mem[size];

    identification(unique_ptr_mem);  //  unique_ptrと表示される(正常)
    identification(shared_ptr_mem);  //  shared_ptrと表示される(正常)
    identification(weak_ptr_mem);    //    weak_ptrと表示される(正常)
    identification(pointer_mem);     //     pointerと表示される(正常)
    identification(array_mem);       //       arrayと表示される(正常)
    identification(vector_mem);      //      etcと表示される(正常)

    delete[] pointer_mem;

    //
    //  ここから問題の部分
    //
    std::cout << std::endl << "-----ここからスマートポインタを使った動的配列の部分-----" << std::endl;

    auto heap_array_unique = std::make_unique<int[]>(size);
    auto heap_array_shared = std::make_shared<int[]>(size);
    auto heap_array_weak = std::weak_ptr<int[]>(heap_array_shared);

    identification(heap_array_unique);  //  unique_ptrと表示される!!!!!(正常)
    identification(heap_array_shared);  //  shared_ptrと表示される!!!!!(正常)
    identification(heap_array_weak);    //  weak_ptrと表示される!!!!!(正常)

    return 0;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

配列をパラメータに取るunique_ptrには、operator*が定義されていません(その代わりoperator[]があります)。このため、std::unique_ptr<int[]>には*type1;がないので、その他のテンプレートが使われています。その他のテンプレートの引数は、参照ではないので引数をコピーコンストラクタを使ってコピーしようとしますが、unique_ptrにはコピーコンストラクタがないのでエラーになっています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/05 08:10 編集

    Bearded-Ockhamさん
    御回答誠に有難うございます。

    > 配列をパラメータに取るunique_ptrには、operator*が定義されていません(その代わりoperator[]があります)。このため、std::unique_ptr<int[]>には*type1;がない

    この部分は知りませんでした... とても勉強になります。

    上記を踏まえれば
    identification(heap_array);は

    auto identification(auto...)noexcept{ // その他が入力された場合実行
    std::cout << "etc" << std::endl;
    }
    に推論される。
    しかし引数は参照渡しではないのでコピーが作成されるはずだが
    std::unique_ptrはコピーできないのでエラー発生。
    ということですね。

    requires内の*type1;を消したら
    問題なく動くようになりましたので修正後のコードを
    質問文に追記しておきます。

    的確なご回答誠に有難うございました。

    キャンセル

+1

別解の一つです。質問本文にあるコンセプト定義では「std::XXX_ptrと同じように振る舞えるスマートポインタ型か否か」を判定しますが、発想を変えて直接std::XXX_ptr型との同一判定を行うコンセプトも記述できますね。

template<typename Type> concept IsUniquePtr =
  std::is_same_v<Type, std::unique_ptr<typename Type::element_type, typename Type::deleter_type>>
  || std::is_same_v<Type, std::unique_ptr<typename Type::element_type[], typename Type::deleter_type>>;

template<typename Type> concept IsSharedPtr =
  std::is_same_v<Type, std::shared_ptr<typename Type::element_type>>;

template<typename Type> concept IsWeakPtr =
  std::is_same_v<Type, std::weak_ptr<typename Type::element_type>>;

おまけ:本題からは外れますが、各関数の記述を短縮表記することもできます。(noexceptは適切でないためついでに削除)

template<IsArray Ar>
auto identification(const Ar&)noexcept{
    std::cout << "array" << std::endl;
}
// ↓
auto identification(IsArray auto const &) {
    std::cout << "array" << std::endl;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/06 04:16 編集

    yohhoyさん
    御返事誠に有難うございます。

    > ストリーム出力処理では例外送出される可能性があるため...

    std::coutが例外を飛ばす事があるとは知りませんでした。
    教えて下さりありがとうございます。
    仰る通りstd::coutが例外を飛ばすのならば
    identificationをnoexceptにするのはおかしいですね。

    確認しましたが確かにここに書いてある関数群には一つもnoexceptは書いてないですね...
    https://cpprefjp.github.io/reference/ostream/basic_ostream/op_ostream.html

    今後もっと注意してnoexcept使って行きます。

    しかしその次のstd::terminateの話の中で疑問が生まれました。
    yohhoyさんが掲示してくださったstd::terminateのリファレンス
    の中に

    > noexceptまたはnoexcept(trueに評価される定数式)が
    指定されている関数で例外送出により脱出しようとした場合
    terminate()関数が呼び出される。

    と書かれているのでその逆(noexcept(false))ではterminate()関数が呼び出されない。
    と私は解釈して

    https://wandbox.org/permlink/D6jQqFQDxTFUXdQ7
    上記のコードを作成して実行してみましたが
    コード中のfunc()noexcept(true)でも
    func()noexcept(false)でもどちらも
    異常終了が発生しました。

    やはり私の解釈は間違えているのでしょうか。

    あとyohhoyさんの回答もとても有効で簡潔に書ける
    素晴らしい方法だなと思ったので
    出来ることならyohhoyさんにもベストアンサーを付けたいところですが
    すでにベストアンサーは付いておりますので
    回答の高評価押しておきます。

    どうか御返事お待ちしております。

    キャンセル

  • 2020/08/06 12:16

    > コード中のfunc()noexcept(true)でもfunc()noexcept(false)でもどちらも異常終了が発生しました。
    「どこでstd::terminate」が呼ばれるかが異なります。
    func()を呼び出すmain関数で例外catchを行わなければ、プログラム全体としては結局異常終了してしまいます。
    https://wandbox.org/permlink/8awUSW0EXQ9mvMhR

    (ベストアンサーはお構いなくw)

    キャンセル

  • 2020/08/06 19:43 編集

    yohhoyさん

    確かに掲示していただいたコードで
    noexcept関数内から例外を投げた時のみ
    異常終了が発生することを確認できました。

    noexceptはただの目印では無いことが
    よくわかったのでとても良かったです。

    的確で分かりやすい御回答誠にありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る