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

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

ただいまの
回答率

90.45%

  • C++

    4549questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

std::enable_if の有無

解決済

回答 3

投稿

  • 評価
  • クリップ 6
  • VIEW 656

SaitoAtsushi

score 507

 環境

処理系は clang 6.0.1 と gcc 7.3.0 の 32 ビット版で、 MSYS2 上で使っています。

 問題

using を用いて二次元配列型の別名 matrix を定義したのですが、念のために要素の数として与えられた数値が正の数かどうかを std::enable_if でチェックするようにしたところ、動作が変わってしまいました。

どうしてこのような差が生まれるのでしょう?

 ソースコード

具体的にいうと、以下のコードでマクロで分岐している箇所を切り替えたような場合です。

#include <iostream>
#include <type_traits>

#ifndef ENABLE_SIZE_CHECKING
template<class T, std::size_t Y, std::size_t X>
using matrix = T[Y][X];
#else
template<class T, size_t Y, size_t X>
using matrix = typename std::enable_if<(Y>0)&&(X>0), T[Y][X]>::type;
#endif

template<class T, size_t Y, size_t X>
std::ostream& operator<<(std::ostream& os, const matrix<T, Y, X>& mat) {
  for (auto& i: mat) {
    for (auto j: i) os << j << ',';
    os << std::endl;
  }

  return os;
}

int main() {
  matrix<int, 3, 3> mat;

  for (auto& line: mat) for (auto& element: line) element = 10;
  std::cout << "array rank is "
            << std::rank<decltype(mat)>::value
            << std::endl;
  std::cout << mat <<std::endl;

  return 0;
}

 実行結果

 std::enable_if での判定をしない場合

array rank is 2
10,10,10,
10,10,10,
10,10,10,

 std::enable_if での判定をする場合

array rank is 2
0x65fec0

 補足

上記のコードでトレイトでの判定も入れていることからわかるように変数は確かに二次元配列として定義されていて、暗黙の型変換が入る余地もないはずなのですが、ポインタとして解釈されているような挙動になっているように思われます。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+7

alias template そのものは型ではなく型の別名であるだけです。

よって、

template<class T, size_t Y, size_t X>
using matrix = typename std::enable_if<(Y>0)&&(X>0), T[Y][X]>::type;

template<class T, size_t Y, size_t X>
std::ostream& operator<<(std::ostream& os, const matrix<T, Y, X>& mat);

template<class T, size_t Y, size_t X>
std::ostream& operator<<(std::ostream& os, const typename std::enable_if<(Y>0)&&(X>0), T[Y][X]>::type& mat);

と同値と考えていいです。

ここで、

typename std::enable_if<(Y>0)&&(X>0), T[Y][X]>::type

は依存型名なので関数テンプレートの推論対象ではないです。

結果、operator<<のオーバーロードはADLの候補に挙がらず、int[3][3]はポインタに変換されてoperator<<が解決されたことにより、先頭ポインタのアドレスが表示されたものと考えられます。

template<class T, size_t Y, size_t X>
using matrix = T[Y][X];

とalias templateを記述したときに想定通りオーバーロードが解決されるのは、
T[Y][X]が依存型名ではないため、推論の対象となるからです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

コンパイラは Visual Studio 2017 ですが、同じ現象になりました。

  • std::enable_if で判定した場合、operator<<() のオーバーロード関数は呼ばれていないようです。
  • 配列なので、その値を出力したら先頭アドレスになります。
  • typeid(mat).name() で確認しましたが、どちらの場合もたしかに int[Y][X] になっていました。

以下の関数は、第2引数は配列の参照となっていますが、これは C++ で禁止されているはずです。 なので、エイリアステンプレートを使ったときに、コンパイルエラーにならずにビルドでき、さらにその関数が呼ばれるほうが不思議です。

std::ostream &operator<<(std::ostream &os, const matrix<T, Y, X> &mat)

これは間違いだったため、訂正します。
参照の配列は禁止されているが、配列の参照は合法でした。


EnumHack さんの解説を見ましたが、そういう背景があったのですね。
ADL も今回知ったのですが、C++ は奥が深いです。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/09/21 02:07

    一点明らかな誤りがありますのでそちらの指摘だけ:”配列の参照”と"参照の配列"は別物です。const T &mat[Y][X]は"参照の配列"となるためNGですが、const T (&mat)[Y][X]は"配列の参照"となり合法です。alias templateが using matrix = T[Y][X]; の場合、 const matrix<T, Y, X> &mat は const T (&mat)[Y][X] と等価であり、これは 「3x3 int配列型への参照型」という合法な型です。

    キャンセル

  • 2018/09/21 02:09

    とりあえず C++ が言っているのは「参照の配列がない」ということで、「配列の参照がない」ということではないのでは。コンパイルエラーになる文は、等価な書き換えとしては次のようになるかと。これではコンパイルエラーは出ませんでした。

    std::ostream &operator<<(std::ostream &os, const T (&mat)[Y][X])

    キャンセル

  • 2018/09/21 02:09

    それは配列の参照ではなく参照の配列になっています。
    配列の参照にするには const T (&mat)[Y][X]) というように、括弧で覆う必要があります。

    キャンセル

  • 2018/09/21 12:04

    ご指摘ありがとうございます。
    禁止されているのは array of referece であって、reference to array ではなかったですね。
    いろいろ勘違いしてました。

    キャンセル

+1

回答ではありませんが、以下のように変更し、 clang 6.0.1 で確認したところ意図通りの出力になりました。

template<class T, size_t Y, size_t X, std::enable_if_t<(Y>0)&&(X>0), int> = 0>
using matrix = T[Y][X];

参考までに。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

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

  • C++

    4549questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。