ゆとりーなの日記

日記的な事を書いて行くと思はれる

あれれ〜?コンパイラ毎に結果が違うよ〜?

とあることがきっかけで色々試していたのですが、なんか面白かったのでメモしておきます。

#include <iostream>

void a() {}

int main() {
  std::cout << a << std::endl;
  return 0;
}

上のコード、g++4.5.1とVC++2010で実行結果が違います。
VC++2010

0031125D

g++4.5.1

1

これは多分関数ポインタがvoid*とboolどっちに優先的にマッチするかって話になると思うんですが、どっちが正しいんですかね。
念のためもう一つ検証コードを。

#include <iostream>

void a() {}

void b(void * const b) {
  std::cout << "ptr" << std::endl;
}

void b(const bool b) {
  std::cout << "bool" << std::endl;
}

int main() {
  b(a);
  return 0;
}

結果
VC++2010

ptr

g++4.5.1

bool

やはりVC++とg++ではどっちに行くかが完全に分かれますね。
折角なので更に追加。こっからg++は-std=c++0xが付きます。

#include <iostream>
#include <type_traits>

void a() {}

template <typename T>
  void b(const T t, typename std::enable_if<std::is_pointer<T>::value>::type * = 0) {
  std::cout << "ptr" << std::endl;
}

void b(const bool b) {
  std::cout << "bool" << std::endl;
}

int main() {
  b(a);
  return 0;
}

流石にこれだとどちらも、

ptr

でした。これでboolが出たらびっくりです。
因みにこれだと、

#include <iostream>
#include <type_traits>

void a() {}

template <typename T>
  void b(const T t, typename std::enable_if<std::is_pointer<T>::value>::type * = 0) {
  std::cout << "ptr" << std::endl;
}

template <typename T>
  void b(const T t, typename std::enable_if<std::is_convertible<T, bool>::value>::type * = 0) {
  std::cout << "bool" << std::endl;
}

int main() {
  b(a);
  return 0;
}

どちらもオーバーロードを解決出来ませんでしたとさ。まぁ残当ですね。
最後にこれだと、

#include <iostream>
#include <type_traits>

void a() {}

template <typename T>
  void b(const T t, typename std::enable_if<std::is_convertible<T, void *>::value>::type * = 0) {
  std::cout << "ptr" << std::endl;
}

template <typename T>
  void b(const T t, typename std::enable_if<std::is_convertible<T, bool>::value>::type * = 0) {
  std::cout << "bool" << std::endl;
}

int main() {
  b(a);
  return 0;
}

VC++オーバーロードを解決できず、g++では、

bool

とboolの方を呼び出してました。ここら辺の挙動もどっちが正しいんですかね。
とか長らく色々書いてきましたが上のコードが実はヒントになっていて、根本的にはオーバーロードの優先がどうとかではなく、実は関数ポインタをvoid*に暗黙的に変換できるかという一点に尽きます。
g++では次のようなコードがコンパイルに失敗します。

#include <iostream>

void a() {}

void b(void * const b) {
  std::cout << "ptr" << std::endl;
}

int main() {
  b(a);
  return 0;
}

エラーメッセージを読むと、void (*)()はvoid *に変換できないよと言ってくるのでそもそもg++では最初の例で言えばvoid b(void * const b)はオーバーロード候補ですらなかったのです。
肝心な関数ポインタをvoid*に暗黙的に変換できるかどうかですが、ちょっと見た感じだと、ARMの頃はこの変換は出来たみたいです。で、後にこの変換はC++から削除された様です。というわけで現行C++ではvoid (*)()はvoid *へは明示的にキャストする必要があります。よって、g++の挙動の方が正しいのだと思います。VC++で暗黙の変換が行われるのは例によってARMの頃に合わせてそう実装しちゃったから後方互換性のために残してるとかなのかもしれませんがこのあたりのことはよく分かりません。