オーバーロードされた関数へのポインターを指定するにはどうすればよいですか?


137

オーバーロードされた関数をstd::for_each()アルゴリズムに渡したいのですが。例えば、

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

コンパイラーf()はイテレーター・タイプで解決することを期待します。どうやら、それ(GCC 4.1.2)はそれをしません。それで、どのようにしてf()私が欲しいものを指定できますか?


回答:


137

を使用static_cast<>()fて、関数ポインタ型によって暗黙に指定された関数シグネチャに従って、どちらを使用するかを指定できます。

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

または、これを行うこともできます。

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

fがメンバー関数である場合は、を使用する必要がありますmem_fun。または、ケースについては、このドブドクター博士の記事に記載されているソリューションを使用してください


1
ありがとう!ただし、おそらくそれf()がクラスのメンバーであるという事実が原因で問題がまだあります(上記の編集された例を参照)
davka

9
@the_drow:2番目の方法は実際にははるかに安全です。オーバーロードの1つがなくなると、最初の方法は黙って未定義の動作を行い、2番目の方法はコンパイル時に問題をキャッチします。
Ben Voigt、

3
@BenVoigtうーん、vs2010でこれをテストしましたが、static_castがコンパイル時に問題をキャッチしないケースを見つけることができませんでした。C2440に「この名前のスコープ内の関数はターゲットタイプと一致しません」と表示されました。明確にできますか?
ネイサンモンテレオーネ

5
@ネイサン:私が考えていた可能性がありreinterpret_castます。ほとんどの場合、これに使用されるCスタイルのキャストを参照します。私のルールは、関数ポインタのキャストは危険で不必要なことです(2番目のコードスニペットが示すように、暗黙的な変換が存在します)。
Ben Voigt

3
メンバー関数の場合:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

ラムダスが救いに!(注:C ++ 11が必要です)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

または、ラムダパラメータにdecltypeを使用します。

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

多形ラムダ(C ++ 14)の場合:

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

または、オーバーロードを削除して明確化します(フリー関数でのみ機能します)。

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

ラムダの万歳!実際、過負荷解決の問題に対する優れたソリューションです。(私はこれも考えましたが、水を
濁さ

同じ結果のためのより多くのコード。それはラムダが作られたものではないと思います。
トマーシュZato -復活モニカ

@TomášZato違いは、この答えは機能し、受け入れられた答えは機能しないことです(OPによって投稿された例では、mem_fnand も使用する必要があります。BTW bindもC ++ 11です)。また、私たちが本当にペダナティブになりたいのであれば、[&](char a){ return f(a); }28文字、static_cast<void (A::*)(char)>(&f)35文字です。
Milleniumbug

1
@TomášZatoそこに行くcoliru.stacked-crooked.com/a/1faad53c4de6c233これをより明確にしていないことを確認する方法
milleniumbug

18

なぜうまくいかないのか

コンパイラーf()はイテレーター・タイプで解決することを期待します。どうやら、それ(gcc 4.1.2)はそれをしません。

もしそうならそれは素晴らしいだろう!ただし、次のようfor_eachに宣言された関数テンプレートです。

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

テンプレート控除UnaryFunctionでは、呼び出し時にタイプを選択する必要があります。ただしf、特定のタイプはありません。オーバーロードされた関数であり、fタイプが異なるが多数あります。現在for_each、テンプレートの推定プロセスを支援する方法はありません。必要なものを指定することによりf、テンプレートの推定は失敗します。テンプレートの控除を成功させるには、呼び出しサイトでさらに作業を行う必要があります。

それを修正するための一般的な解決策

ここ数年、そしてC ++ 14後を期待しています。static_cast(使用する「修正」によりテンプレートの推定が成功するfが、正しいものを「修正」するためにオーバーロードの解決を手動で行う必要がある)を使用する代わりに、コンパイラーを機能させたいと考えています。fいくつかの引数を求めたいと思います。可能な最も一般的な方法では、それは次のとおりです。

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

入力するのは大変ですが、この種の問題は迷惑なほど頻繁に発生するため、マクロ(溜息)でラップするだけです。

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

そしてそれを使う:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

これは、コンパイラがしたいことを正確に実行します-名前f自体にオーバーロード解決を実行し、正しいことを行います。これfは、フリー関数かメンバー関数かに関係なく機能します。


7

あなたの質問には答えませんが、私が見つけたのは私だけです

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

for_eachこの場合、インシリコで提案された代替案よりも単純で短いですか?


2
おそらく、それは退屈です:)また、イテレータを使用して[]演算子を回避したい場合、これは長くなります...
davka

3
@Davka Boringは私たちが望んでいるものです。また、問題がある場合は、通常、反復子はop [を使用するよりも速くありません(遅くなる可能性があります)。

7
エラーが発生しにくく、最適化の機会が増えるため、アルゴリズムはforループよりも推奨されます。どこかにそれについての記事があります...ここにあります:drdobbs.com/184401446
AshleysBrain

5
@Ashley「エラーが発生しにくい」に関する客観的な統計を見るまでは、信じる必要はありません。そして、記事のマイヤーズはイテレータを使用するループについて話しているようです-私はイテレータを使用しないループの効率について話している-私自身のベンチマークは、最適化されたときにこれらがわずかに速いことを示唆する傾向があります-確かに遅くはありません。

1
ここに私がいます、あなたの解決策もはるかに優れています。
peterh-2016年

5

ここでの問題は、オーバーロードの解決ではなく、実際にはテンプレートパラメータの差し引きであるようです。@In silicoからの優れた答えは一般的にあいまいなオーバーロードの問題を解決しますが、std::for_each(または同様の)を処理するときの最良の修正は、テンプレートパラメーター明示的に指定することです:

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}

4

C ++ 11を使用してもかまわない場合は、静的キャストに似た(ただし、見苦しくない)賢いヘルパーを次に示します。

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(メンバ関数のための作品は、機能の自立のための仕事にそれを修正する方法を明らかにする必要があり、あなたがすべきあり、両方のバージョンを提供できれ、コンパイラーが適切なバージョンを選択します。)

提案してくれたMiro Knejpに感謝します。https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4Jも参照してください


OPの問題は、オーバーロードされた名前を関数テンプレートに渡すことができないことであり、ソリューションには、オーバーロードされた名前を関数テンプレートに渡すことが含まれますか?これはまったく同じ問題です。
バリー

1
@バリー同じ問題ではありません。この場合、テンプレート引数の推定は成功します。それは動作します(いくつかのマイナーな微調整で)。
オクタリスト2016

@Oktalistを提供しているためR、推定されていません。この回答では、そのことについての言及もありません。
バリー

1
@Barry提供していません。R提供していArgsます。RT推定されます。答えが改善される可能性があるのは事実です。(Tただし、これはメンバーへのポインターではないため、私の例にはありませんstd::for_each。それは。で動作しないためです。)
Oktalist
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.