bug 80985 からのこの例を考えてください:
template <class Func>
void call(Func f)
{
f();
}
void func() noexcept { }
int main()
{
call(func);
}
すべての警告を有効にしてこれをコンパイルすると、次のようになります。
$ g++ -std=c++14 -Wall foo.cxx
foo.cxx:2:6: warning: mangled name for ‘void call(Func) [with Func = void (*)() noexcept]’ will change in C++17 because the exception specification is part of a function type [-Wnoexcept-type]
void call(Func f)
^~~~
この警告はどうすればいいのですか?修正は何ですか?
警告メッセージについてできることはいくつかあります。
_-Wno-noexcept-type
_で無効にします。多くのプロジェクトでは、結果のオブジェクトが、GCCのC++ 17名前マングリングを使用することを期待する別のオブジェクトとリンクされる可能性がないため、警告メッセージは役に立ちません。異なる_-std=
_設定でコンパイルしておらず、違反関数がパブリックインターフェイスの一部である静的または共有ライブラリを構築していない場合、警告メッセージは安全に無効にできます。
_-std=c++17
_を使用してすべてのコードをコンパイルします。関数が新しいマングル名を使用するため、警告メッセージは消えます。
関数をstatic
にします。関数は、関数の別のマングリングを使用して別のオブジェクトファイルから参照できなくなるため、警告メッセージは表示されません。関数定義は、それを使用するすべてのコンパイル単位に含める必要がありますが、例のようなテンプレート関数では、これはとにかく一般的です。また、static
が他の何かを意味する場合、これはメンバー関数では機能しません。
関数テンプレートを呼び出すときは、例外の仕様を持たない互換性のある関数ポインター型を明示的に与えるテンプレートパラメーターを指定します。たとえば、call<void (*)()>(func)
。キャストを使用してこれを行うこともできますが、GCC 7.2.0は_-std=c++17
_を使用してもマングリングが変更されない場合でも警告を生成します。
関数がテンプレートでない場合、関数の型で使用される関数ポインター型でnoexcept
を使用しないでください。この点と最後の点は、スローしない関数ポインタータイプのみが命名の変更をもたらすという事実と、スローしない関数ポインターをスロー(スロー)に割り当て(C++ 11)または暗黙的に変換(C++ 17)できるという事実に依存しています関数ポインタ。
call<void (*)()>(func)
ソリューションに対するRossの答えを支持しています。 noexcept
以外の関数型に対してテンプレートをインスタンス化することをコンパイラーに明示的に指示し、C++ 14で行ったのとまったく同じようにコードがC++ 17で動作することを保証します。
その他の選択肢は次のとおりです。
(1)noexcept
関数をラムダでラップします(noexcept
ではありません):
template <class Func>
void call(Func f)
{
f();
}
void func() noexcept { }
int main()
{
call([]() { func(); });
}
(2)noexcept
なしで個別のラッパー関数を作成します。これは最初はより多くの入力を行いますが、複数の呼び出しサイトがある場合、全体的に入力を節約できます。これは、最初にGCCバグを報告するように促したコードの 私がやったこと です。
既に述べたことに加えて、GCC 7でこの警告を取り除く別の方法を見つけました。明らかに、call()
の最初のインスタンス化がnoexcept
を含む場合にのみ、GCCはこの警告を生成します。したがって、解決策は、noexcept
ではない関数を使用してcall()
をインスタンス化することです。
このトリックは次のことも行います。
using dummy = decltype(call(std::declval<void(*)()>()));
追伸この場合、GCC 8.2.1は警告を報告しません。
彼らが警告している問題は、C++ 14ではこれが機能するということです。
void call(void (*f)())
{
f();
}
void func() noexcept {}
int main(int argc, char* argv[])
{
call(&func);
return 0;
}
ただし、C++ 17では、call
の宣言を次のように変更する必要があります。
void call(void (*f)() noexcept)
{
f();
}
call
をテンプレートとして定義したので、これについて心配する必要はありません。それでも、推測される型が変更されるため、問題が発生する可能性がありますが、通常は発生しません。
たとえば、次のコードはC++ 14ではコンパイルできますが、C++ 17ではコンパイルできません。
void foo() noexcept {}
void bar() {}
template <typename F>
void call(bool b, F f1, F f2)
{
if (b)
f1();
else
f2();
}
void foobar(bool b)
{
call(b, &foo, &bar);
}
C++ 14では、foo
とbar
のタイプは同じですが、C++ 17では異なるため、テンプレートの解決は失敗します。フラグ-std=c++1z
を含むgcc 7.2のエラーメッセージは次のとおりです。
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'F' ('void (*)() noexcept' and 'void (*)()')
指定した例では、問題はなく、C++ 14またはC++ 17モードでのコンパイルに問題はありません。コードがここの例よりも複雑な場合(たとえば、上記の例と同様)、コンパイラの問題が発生する可能性があります。最近のコンパイラがあるようです。 -std=c++1z
でコンパイルしてみて、警告またはエラーがあるかどうかを確認してください。